/// <summary> /// Starts the client transaction. /// </summary> public void Start() { SipCSeqValue vCSeq; // Add a CSeq header to the request if necessary and intialize the // transaction's local sequence number. vCSeq = request.GetHeader <SipCSeqValue>(SipHeader.CSeq); if (vCSeq == null) { request.AddHeader(SipHeader.CSeq, new SipCSeqValue(SipHelper.GenCSeq(), request.MethodText)); } // Add Max-Forwards if necessary. if (request.GetHeaderText(SipHeader.MaxForwards) == null) { request.AddHeader(SipHeader.MaxForwards, SipHelper.MaxForwards); } // Start the transaction using (TimedLock.Lock(agent)) { SetState(request.Method == SipMethod.Invite ? SipTransactionState.InviteCalling : SipTransactionState.Trying); transport.Send(remoteEP, request); } }
/// <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> /// 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> /// 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); }