예제 #1
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       callOnInviteComplete = false;
            SipRequest callbackMsg          = null;

            try
            {
                using (TimedLock.Lock(agent))
                {
                    if (SysTime.Now >= ttd)
                    {
                        SetState(SipTransactionState.Terminated);
                        return;
                    }

                    switch (base.State)
                    {
                    default:
                    case SipTransactionState.Unknown:

                        break;

                    case SipTransactionState.InviteCalling:

                        break;

                    case SipTransactionState.InviteProceeding:

                        break;

                    case SipTransactionState.InviteCompleted:

                        if (base.TimerH.HasFired)
                        {
                            SetState(SipTransactionState.Terminated);

                            // Setup to call OnInviteComplete(ackRequest=null)
                            // indicating that the dialog was not established.

                            callOnInviteComplete = true;
                            return;
                        }

                        if (base.TimerG.HasFired)
                        {
                            transport.Send(remoteEP, finalResponse);
                            base.TimerG.Interval = Helper.Min(base.BaseTimers.T2, Helper.Multiply(base.TimerG.Interval, 2));
                        }
                        break;

                    case SipTransactionState.InviteConfirmed:

                        if (base.TimerI.HasFired)
                        {
                            SetState(SipTransactionState.Terminated);
                        }

                        break;

                    case SipTransactionState.Trying:

                        break;

                    case SipTransactionState.Proceeding:

                        break;

                    case SipTransactionState.Completed:

                        if (base.TimerJ.HasFired)
                        {
                            SetState(SipTransactionState.Terminated);
                        }

                        break;

                    case SipTransactionState.Terminated:

                        break;
                    }
                }
            }
            finally
            {
                // Handle the agent callbacks outside of the lock to avoid
                // deadlock issues.

                if (callOnInviteComplete)
                {
                    agent.OnInviteComplete(this, request, finalResponse, callbackMsg);
                }
            }
        }
예제 #2
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);
                }
            }
        }
예제 #3
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;
 }
예제 #4
0
        /// <summary>
        /// Executes a synchronous SIP request transaction.
        /// </summary>
        /// <param name="request">The <see cref="SipRequest" /> to be submitted.</param>
        /// <param name="dialog">The <see cref="SipDialog" /> for requests that initiate a dialog (or <c>null</c>).</param>
        /// <returns>The <see cref="SipResult" /> detailing the result of the operation.</returns>
        /// <remarks>
        /// <note>
        /// This method adds reasonable <b>Call-ID</b> and <b>CSeq</b> headers to the request if these
        /// headers are not already present.
        /// </note>
        /// </remarks>
        public SipResult Request(SipRequest request, SipDialog dialog)
        {
            var ar = BeginRequest(request, dialog, null, null);

            return(EndRequest(ar));
        }
예제 #5
0
        /// <summary>
        /// Initiates an asynchronous SIP request transaction.
        /// </summary>
        /// <param name="request">The <see cref="SipRequest" /> to be submitted.</param>
        /// <param name="dialog">The <see cref="SipDialog" /> for requests that initiate a dialog (or <c>null</c>).</param>
        /// <param name="callback">The delegate to be called when the operation completes (or <c>null</c>).</param>
        /// <param name="state">Application defined state (or <c>null</c>).</param>
        /// <returns>The <see cref="IAsyncResult" /> to be used to track the operation's progress.</returns>
        /// <remarks>
        /// <para>
        /// All requests to <see cref="BeginRequest(SipRequest,SipDialog,AsyncCallback,object)" /> must be matched with a
        /// call to <see cref="EndRequest" />.
        /// </para>
        /// <note>
        /// This method adds reasonable <b>Call-ID</b> and <b>CSeq</b> headers to the request if these
        /// headers are not already present.
        /// </note>
        /// </remarks>
        public IAsyncResult BeginRequest(SipRequest request, SipDialog dialog, AsyncCallback callback, object state)
        {
            ClientAsyncResult    arClient = new ClientAsyncResult(request, dialog, callback, state);
            SipValue             viaValue;
            SipCSeqValue         vCSeq;
            string               transactionID;
            SipClientTransaction transaction;
            ISipTransport        transport;
            NetworkBinding       remoteEP;

            if (dialog != null && request.Method != SipMethod.Invite)
            {
                throw new InvalidOperationException("Dialogs may be created only for INVITE requests.");
            }

            arClient.Dialog = dialog;

            transport = router.SelectTransport(this, request, out remoteEP);
            if (transport == null)
            {
                throw new SipException("No approriate transport is available.");
            }

            // Initialize the request's Via header and transaction ID as necessary.

            transactionID      = SipHelper.GenerateBranchID();
            viaValue           = new SipValue(string.Format("SIP/2.0/{0} {1}", transport.Name, transport.Settings.ExternalBinding.Address));
            viaValue["branch"] = transactionID;
            viaValue["rport"]  = string.Empty;

            request.PrependHeader(SipHeader.Via, viaValue);

            // Initialize common headers as necessary

            if (!request.ContainsHeader(SipHeader.CallID))
            {
                request.AddHeader(SipHeader.CallID, SipHelper.GenerateCallID());
            }

            vCSeq = request.GetHeader <SipCSeqValue>(SipHeader.CSeq);
            if (vCSeq == null)
            {
                vCSeq = new SipCSeqValue(SipHelper.GenCSeq(), request.MethodText);
                request.AddHeader(SipHeader.CSeq, vCSeq);
            }

            // Initialize the transaction

            transaction            = new SipClientTransaction(this, request, transactionID, transport, remoteEP);
            transaction.AgentState = arClient;

            // Handle initial dialog INVITE specific initialization

            if (dialog != null && request.Method == SipMethod.Invite && dialog.State == SipDialogState.Waiting)
            {
                // Client-side dialogs need to know the transaction so
                // they'll be able to send the confirming ACK.

                dialog.InitiatingTransaction = transaction;

                // Dialogs need to know about the sequence number used in INVITE requests so
                // that the ACK can be generated with the same sequence number.

                dialog.AckCSeq = vCSeq.Number;

                // The dialog has been intialized enough to be added to the core's
                // early dialog table.

                core.AddEarlyDialog(dialog);
            }

            // Start the transaction

            using (TimedLock.Lock(this))
            {
                transactions.Add(transactionID, transaction);
            }

            transaction.Start();
            arClient.Started();

            return(arClient);
        }
예제 #6
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);
        }