/// <summary> /// Creates a CANCEL <see cref="SipRequest" /> for this request. /// </summary> /// <returns>The CANCEL <see cref="SipRequest" />.</returns> public SipRequest CreateCancelRequest() { SipRequest cancelRequest = new SipRequest(SipMethod.Cancel, this.uri, this.SipVersion); SipHeader header; SipCSeqValue vCSeq; header = this[SipHeader.Via]; if (header == null || header.Values.Length == 0) { throw new SipException("[Via] header is required when generating a CANCEL."); } cancelRequest.SetHeader(SipHeader.Via, header.Text); header = this[SipHeader.Route]; if (header != null) { cancelRequest.Headers.Add(SipHeader.Route, header); } header = this[SipHeader.To]; if (header != null) { cancelRequest.Headers.Add(SipHeader.To, header.Clone()); } header = this[SipHeader.From]; if (header != null) { cancelRequest.Headers.Add(SipHeader.From, header.Clone()); } header = this[SipHeader.CallID]; if (header == null) { throw new SipException("[Call-ID] header is required when generating a CANCEL."); } cancelRequest.Headers.Add(SipHeader.CallID, header.Clone()); vCSeq = this.GetHeader <SipCSeqValue>(SipHeader.CSeq); if (vCSeq == null) { throw new SipException("[CSeq] header is required when generating a CANCEL."); } cancelRequest.SetCSeq(vCSeq.Number); return(cancelRequest); }
/// <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); } } }