/// <summary> /// Cleans up any resource being used. /// </summary> public void Dispose() { lock (m_pLock) { if (this.State == SIP_CallState.Disposed) { return; } SetState(SIP_CallState.Disposed); // TODO: Clean up m_pStack = null; m_pLocalSDP = null; if (m_pDialog != null) { m_pDialog.Dispose(); m_pDialog = null; } m_pFlow = null; if (m_pKeepAliveTimer != null) { m_pKeepAliveTimer.Dispose(); m_pKeepAliveTimer = null; } this.StateChanged = null; } }
private RTP_Address GetRtpTarget(SDP_Message sdp, SDP_MediaDescription mediaStream) { if (sdp == null) { throw new ArgumentNullException("sdp"); } if (mediaStream == null) { throw new ArgumentNullException("mediaStream"); } // We must have SDP global or per media connection info. string host = mediaStream.Connection != null ? mediaStream.Connection.Address : null; if (host == null) { host = sdp.Connection.Address != null ? sdp.Connection.Address : null; if (host == null) { throw new ArgumentException("Invalid SDP message, global or per media 'c'(Connection) attribute is missing."); } } int remoteRtcpPort = mediaStream.Port + 1; // Use specified RTCP port, if specified. foreach (SDP_Attribute attribute in mediaStream.Attributes) { if (string.Equals(attribute.Name, "rtcp", StringComparison.InvariantCultureIgnoreCase)) { remoteRtcpPort = Convert.ToInt32(attribute.Value); break; } } return new RTP_Address(System.Net.Dns.GetHostAddresses(host)[0], mediaStream.Port, remoteRtcpPort); }
/// <summary> /// Incoming call constructor. /// </summary> /// <param name="stack">Reference to SIP stack.</param> /// <param name="dialog">Reference SIP dialog.</param> /// <param name="session">Call RTP multimedia session.</param> /// <param name="localSDP">Local SDP.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>dialog</b>,<b>session</b> or <b>localSDP</b> is null reference.</exception> internal SIP_Call(SIP_Stack stack, SIP_Dialog dialog, RTP_MultimediaSession session, SDP_Message localSDP) { if (stack == null) { throw new ArgumentNullException("stack"); } if (dialog == null) { throw new ArgumentNullException("dialog"); } if (session == null) { throw new ArgumentNullException("session"); } if (localSDP == null) { throw new ArgumentNullException("localSDP"); } m_pStack = stack; m_pDialog = (SIP_Dialog_Invite)dialog; m_pRtpMultimediaSession = session; m_pLocalSDP = localSDP; m_StartTime = DateTime.Now; m_pFlow = dialog.Flow; dialog.StateChanged += new EventHandler(m_pDialog_StateChanged); SetState(SIP_CallState.Active); // Start ping timer. m_pKeepAliveTimer = new TimerEx(40000); m_pKeepAliveTimer.Elapsed += new System.Timers.ElapsedEventHandler(m_pKeepAliveTimer_Elapsed); m_pKeepAliveTimer.Enabled = true; }
private RTP_StreamMode GetRtpStreamMode(SDP_Message sdp, SDP_MediaDescription media) { if (sdp == null) { throw new ArgumentNullException("sdp"); } if (media == null) { throw new ArgumentNullException("media"); } // Try to get per media stream mode. foreach (SDP_Attribute a in media.Attributes) { if (string.Equals(a.Name, "sendrecv", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.SendReceive; } else if (string.Equals(a.Name, "sendonly", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.Send; } else if (string.Equals(a.Name, "recvonly", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.Receive; } else if (string.Equals(a.Name, "inactive", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.Inactive; } } // No per media stream mode, try to get per session stream mode. foreach (SDP_Attribute a in sdp.Attributes) { if (string.Equals(a.Name, "sendrecv", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.SendReceive; } else if (string.Equals(a.Name, "sendonly", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.Send; } else if (string.Equals(a.Name, "recvonly", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.Receive; } else if (string.Equals(a.Name, "inactive", StringComparison.InvariantCultureIgnoreCase)) { return RTP_StreamMode.Inactive; } } return RTP_StreamMode.SendReceive; }
private string GetSdpHost(SDP_Message sdp, SDP_MediaDescription mediaStream) { if (sdp == null) { throw new ArgumentNullException("sdp"); } if (mediaStream == null) { throw new ArgumentNullException("mediaStream"); } // We must have SDP global or per media connection info. string host = mediaStream.Connection != null ? mediaStream.Connection.Address : null; if (host == null) { host = sdp.Connection.Address != null ? sdp.Connection.Address : null; if (host == null) { throw new ArgumentException("Invalid SDP message, global or per media 'c'(Connection) attribute is missing."); } } return host; }
/// <summary> /// Sends ringing to remote party. /// </summary> /// <param name="sdp">Early media answer or early media offer when initial INVITE don't have SDP.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception> public void SendRinging(SDP_Message sdp) { if(m_State == SIP_UA_CallState.Disposed){ throw new ObjectDisposedException(this.GetType().Name); } if(m_State != SIP_UA_CallState.WaitingToAccept){ throw new InvalidOperationException("Accept method can be called only in 'SIP_UA_CallState.WaitingToAccept' state."); } SIP_Response response = m_pUA.Stack.CreateResponse(SIP_ResponseCodes.x180_Ringing,m_pInitialInviteTransaction.Request,m_pInitialInviteTransaction.Flow); if(sdp != null){ response.ContentType = "application/sdp"; response.Data = sdp.ToByte(); m_pLocalSDP = sdp; } m_pInitialInviteTransaction.SendResponse(response); }
private void ProcessMediaOffer(SIP_Dialog dialog, SIP_ServerTransaction transaction, RTP_MultimediaSession rtpMultimediaSession, SDP_Message offer, SDP_Message localSDP) { if (dialog == null) { throw new ArgumentNullException("dialog"); } if (transaction == null) { throw new ArgumentNullException("transaction"); } if (rtpMultimediaSession == null) { throw new ArgumentNullException("rtpMultimediaSession"); } if (offer == null) { throw new ArgumentNullException("offer"); } if (localSDP == null) { throw new ArgumentNullException("localSDP"); } try { #region SDP basic validation // Version field must exist. if (offer.Version == null) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Required 'v'(Protocol Version) field is missing.", transaction.Request)); return; } // Origin field must exist. if (offer.Origin == null) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Required 'o'(Origin) field is missing.", transaction.Request)); return; } // Session Name field. // Check That global 'c' connection attribute exists or otherwise each enabled media stream must contain one. if (offer.Connection == null) { for (int i = 0; i < offer.MediaDescriptions.Count; i++) { if (offer.MediaDescriptions[i].Connection == null) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Global or per media stream no: " + i + " 'c'(Connection) attribute is missing.", transaction.Request)); return; } } } #endregion // Re-INVITE media streams count must be >= current SDP streams. if (localSDP.MediaDescriptions.Count > offer.MediaDescriptions.Count) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": re-INVITE SDP offer media stream count must be >= current session stream count.", transaction.Request)); return; } bool audioAccepted = false; // Process media streams info. for (int i = 0; i < offer.MediaDescriptions.Count; i++) { SDP_MediaDescription offerMedia = offer.MediaDescriptions[i]; SDP_MediaDescription answerMedia = (localSDP.MediaDescriptions.Count > i ? localSDP.MediaDescriptions[i] : null); // Disabled stream. if (offerMedia.Port == 0) { // Remote-party offered new disabled stream. if (answerMedia == null) { // Just copy offer media stream data to answer and set port to zero. localSDP.MediaDescriptions.Add(offerMedia); localSDP.MediaDescriptions[i].Port = 0; } // Existing disabled stream or remote party disabled it. else { answerMedia.Port = 0; #region Cleanup active RTP stream and it's resources, if it exists // Dispose existing RTP session. if (answerMedia.Tags.ContainsKey("rtp_session")) { ((RTP_Session)offerMedia.Tags["rtp_session"]).Dispose(); answerMedia.Tags.Remove("rtp_session"); } // Release UPnPports if any. if (answerMedia.Tags.ContainsKey("upnp_rtp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)answerMedia.Tags["upnp_rtp_map"]); } catch { } answerMedia.Tags.Remove("upnp_rtp_map"); } if (answerMedia.Tags.ContainsKey("upnp_rtcp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)answerMedia.Tags["upnp_rtcp_map"]); } catch { } answerMedia.Tags.Remove("upnp_rtcp_map"); } #endregion } } // Remote-party wants to communicate with this stream. else { // See if we can support this stream. if (!audioAccepted && CanSupportMedia(offerMedia)) { // New stream. if (answerMedia == null) { answerMedia = new SDP_MediaDescription(SDP_MediaTypes.audio, 0, 2, "RTP/AVP", null); localSDP.MediaDescriptions.Add(answerMedia); } #region Build audio codec map with codecs which we support Dictionary<int, AudioCodec> audioCodecs = GetOurSupportedAudioCodecs(offerMedia); answerMedia.MediaFormats.Clear(); answerMedia.Attributes.Clear(); foreach (KeyValuePair<int, AudioCodec> entry in audioCodecs) { answerMedia.Attributes.Add(new SDP_Attribute("rtpmap", entry.Key + " " + entry.Value.Name + "/" + entry.Value.CompressedAudioFormat.SamplesPerSecond)); answerMedia.MediaFormats.Add(entry.Key.ToString()); } answerMedia.Attributes.Add(new SDP_Attribute("ptime", "20")); answerMedia.Tags["audio_codecs"] = audioCodecs; #endregion #region Create/modify RTP session // RTP session doesn't exist, create it. if (!answerMedia.Tags.ContainsKey("rtp_session")) { RTP_Session rtpSess = CreateRtpSession(rtpMultimediaSession); // RTP session creation failed,disable this stream. if (rtpSess == null) { answerMedia.Port = 0; break; } answerMedia.Tags.Add("rtp_session", rtpSess); rtpSess.NewReceiveStream += delegate(object s, RTP_ReceiveStreamEventArgs e) { if (answerMedia.Tags.ContainsKey("rtp_audio_out")) { ((AudioOut_RTP)answerMedia.Tags["rtp_audio_out"]).Dispose(); } AudioOut_RTP audioOut = new AudioOut_RTP(m_pAudioOutDevice, e.Stream, audioCodecs); audioOut.Start(); answerMedia.Tags["rtp_audio_out"] = audioOut; }; // NAT if (!HandleNAT(answerMedia, rtpSess)) { // NAT handling failed,disable this stream. answerMedia.Port = 0; break; } } RTP_StreamMode offerStreamMode = GetRtpStreamMode(offer, offerMedia); if (offerStreamMode == RTP_StreamMode.Inactive) { answerMedia.SetStreamMode("inactive"); } else if (offerStreamMode == RTP_StreamMode.Receive) { answerMedia.SetStreamMode("sendonly"); } else if (offerStreamMode == RTP_StreamMode.Send) { answerMedia.SetStreamMode("recvonly"); } else if (offerStreamMode == RTP_StreamMode.SendReceive) { answerMedia.SetStreamMode("sendrecv"); } RTP_Session rtpSession = (RTP_Session)answerMedia.Tags["rtp_session"]; rtpSession.Payload = Convert.ToInt32(answerMedia.MediaFormats[0]); rtpSession.StreamMode = GetRtpStreamMode(localSDP, answerMedia); rtpSession.RemoveTargets(); if (GetSdpHost(offer, offerMedia) != "0.0.0.0") { rtpSession.AddTarget(GetRtpTarget(offer, offerMedia)); } rtpSession.Start(); #endregion #region Create/modify audio-in source if (!answerMedia.Tags.ContainsKey("rtp_audio_in")) { AudioIn_RTP rtpAudioIn = new AudioIn_RTP(m_pAudioInDevice, 20, audioCodecs, rtpSession.CreateSendStream()); rtpAudioIn.Start(); answerMedia.Tags.Add("rtp_audio_in", rtpAudioIn); } else { ((AudioIn_RTP)answerMedia.Tags["rtp_audio_in"]).AudioCodecs = audioCodecs; } #endregion audioAccepted = true; } // We don't accept this stream, so disable it. else { // Just copy offer media stream data to answer and set port to zero. // Delete exisiting media stream. if (answerMedia != null) { localSDP.MediaDescriptions.RemoveAt(i); } localSDP.MediaDescriptions.Add(offerMedia); localSDP.MediaDescriptions[i].Port = 0; } } } #region Create and send 2xx response SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, transaction.Request, transaction.Flow); //response.Contact = SIP stack will allocate it as needed; response.ContentType = "application/sdp"; response.Data = localSDP.ToByte(); transaction.SendResponse(response); // Start retransmitting 2xx response, while ACK receives. Handle2xx(dialog, transaction); // REMOVE ME: 27.11.2010 // Start retransmitting 2xx response, while ACK receives. //m_pInvite2xxMgr.Add(dialog,transaction); #endregion } catch (Exception x) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, transaction.Request)); } }
private void m_pStack_RequestReceived(object sender, SIP_RequestReceivedEventArgs e) { try { #region CANCEL if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); } else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } } #endregion #region BYE else if (e.Request.RequestLine.Method == SIP_Methods.BYE) { // Currently we match BYE to dialog and it processes it, // so BYE what reaches here doesnt match to any dialog. e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } #endregion #region INVITE else if (e.Request.RequestLine.Method == SIP_Methods.INVITE) { #region Incoming call if (e.Dialog == null) { #region Validate incoming call // We don't accept more than 1 call at time. if (connected || m_pCall != null) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x600_Busy_Everywhere, e.Request)); return; } // We don't accept SDP offerless calls. if (e.Request.ContentType == null || e.Request.ContentType.ToLower().IndexOf("application/sdp") == -1) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x606_Not_Acceptable + ": We don't accpet SDP offerless calls.", e.Request)); return; } SDP_Message sdpOffer = SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data)); // Check if we can accept any media stream. bool canAccept = false; foreach (SDP_MediaDescription media in sdpOffer.MediaDescriptions) { if (CanSupportMedia(media)) { canAccept = true; break; } } if (!canAccept) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x606_Not_Acceptable, e.Request)); return; } #endregion // Send ringing to remote-party. SIP_Response responseRinging = m_pStack.CreateResponse(SIP_ResponseCodes.x180_Ringing, e.Request, e.Flow); responseRinging.To.Tag = SIP_Utils.CreateTag(); e.ServerTransaction.SendResponse(responseRinging); SIP_Dialog_Invite dialog = (SIP_Dialog_Invite)m_pStack.TransactionLayer.GetOrCreateDialog(e.ServerTransaction, responseRinging); // We need invoke here, otherwise we block SIP stack RequestReceived event while incoming call UI showed. this.BeginInvoke(new MethodInvoker(delegate() { try { //m_pPlayer.Play(ResManager.GetStream("ringing.wav"), 20); // Call accepted. RTP_MultimediaSession rtpMultimediaSession = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME()); // Build local SDP template SDP_Message sdpLocal = new SDP_Message(); sdpLocal.Version = "0"; sdpLocal.Origin = new SDP_Origin("-", sdpLocal.GetHashCode(), 1, "IN", "IP4", System.Net.Dns.GetHostAddresses("")[0].ToString()); sdpLocal.SessionName = "SIP Call"; sdpLocal.Times.Add(new SDP_Time(0, 0)); ProcessMediaOffer(dialog, e.ServerTransaction, rtpMultimediaSession, sdpOffer, sdpLocal); // Create call. m_pCall = new SIP_Call(m_pStack, dialog, rtpMultimediaSession, sdpLocal); m_pCall.StateChanged += new EventHandler(m_pCall_StateChanged); m_pCall_StateChanged(m_pCall, new EventArgs()); if (m_IsDebug) { wfrm_RTP_Debug rtpDebug = new wfrm_RTP_Debug(m_pCall.RtpMultimediaSession); rtpDebug.Show(); } connected = true; } catch (Exception x1) { MessageBox.Show("Error: " + x1.Message, "Error:", MessageBoxButtons.OK, MessageBoxIcon.Error); connected = false; m_pConnect.Image = global::PowerSDR.Properties.Resources.call; } })); } #endregion #region Re-INVITE else { try { // Remote-party provided SDP offer. if (e.Request.ContentType != null && e.Request.ContentType.ToLower().IndexOf("application/sdp") > -1) { ProcessMediaOffer(m_pCall.Dialog, e.ServerTransaction, m_pCall.RtpMultimediaSession, SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data)), m_pCall.LocalSDP); // Remote-party is holding a call. if (IsRemotePartyHolding(SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data)))) { // We need invoke here, we are running on thread pool thread. this.BeginInvoke(new MethodInvoker(delegate() { m_pStatusBar.Items[0].Text = "Remote party holding a call"; })); //m_pPlayer.Play(ResManager.GetStream("onhold.wav"), 20); } // Call is active. else { // We need invoke here, we are running on thread pool thread. this.BeginInvoke(new MethodInvoker(delegate() { m_pStatusBar.Items[0].Text = "Call established"; })); m_pPlayer.Stop(); } } // Error: Re-INVITE can't be SDP offerless. else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Re-INVITE must contain SDP offer.", e.Request)); } } catch (Exception x1) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x1.Message, e.Request)); } } #endregion } #endregion #region ACK else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { // Abandoned ACK, just skip it. } #endregion #region MESSAGE else if (e.Request.RequestLine.Method == SIP_Methods.MESSAGE) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); byte[] msg = e.Request.Data; ASCIIEncoding buffer = new ASCIIEncoding(); string data = buffer.GetString(msg); string answer = ""; if (debug && !console.ConsoleClosing) console.Invoke(new DebugCallbackFunction(console.DebugCallback), data); if (op_mode == VoIP_mode.Server) answer = console.CAT_server_socket.ProcessData(msg, msg.Length); else { if (console.CAT_client_socket.ProcessData(msg, msg.Length, out answer)) SendMessage(answer, "CAT"); } } #endregion #region Other else { // ACK is response less method. if (e.Request.RequestLine.Method != SIP_Methods.ACK) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented, e.Request)); } } #endregion } catch { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error, e.Request)); } }
private bool IsRemotePartyHolding(SDP_Message sdp) { if (sdp == null) { throw new ArgumentNullException("sdp"); } // Check if first audio stream is SendRecv, otherwise remote-party holding audio. foreach (SDP_MediaDescription media in sdp.MediaDescriptions) { if (media.Port != 0 && media.MediaType == "audio") { if (GetRtpStreamMode(sdp, media) != RTP_StreamMode.SendReceive) { return true; } break; } } return false; }
/// <summary> /// Parses SDP from raw data. /// </summary> /// <param name="data">Raw SDP data.</param> /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception> public static SDP_Message Parse(string data) { if(data == null){ throw new ArgumentNullException("data"); } SDP_Message sdp = new SDP_Message(); System.IO.StringReader r = new System.IO.StringReader(data); string line = r.ReadLine(); //--- Read global fields --------------------------------------------- while(line != null){ line = line.Trim(); // We reached to media descriptions if(line.ToLower().StartsWith("m")){ /* m= (media name and transport address) i=* (media title) c=* (connection information -- optional if included at session level) b=* (zero or more bandwidth information lines) k=* (encryption key) a=* (zero or more media attribute lines) */ SDP_MediaDescription media = SDP_MediaDescription.Parse(line); sdp.m_pMediaDescriptions.Add(media); line = r.ReadLine(); // Pasrse media fields and attributes while(line != null){ line = line.Trim(); // Next media descrition, just stop active media description parsing, // fall through main while, allow next while loop to process it. if(line.ToLower().StartsWith("m")){ break; } // i media title else if(line.ToLower().StartsWith("i")){ media.Information = line.Split(new char[]{'='},2)[1].Trim(); } // c connection information else if(line.ToLower().StartsWith("c")){ media.Connection = SDP_Connection.Parse(line); } // a Attributes else if(line.ToLower().StartsWith("a")){ media.Attributes.Add(SDP_Attribute.Parse(line)); } line = r.ReadLine(); } if(line == null){ break; } else{ continue; } } // v Protocol Version else if(line.ToLower().StartsWith("v")){ sdp.Version = line.Split(new char[]{'='},2)[1].Trim(); } // o Origin else if(line.ToLower().StartsWith("o")){ sdp.Origin = SDP_Origin.Parse(line); } // s Session Name else if(line.ToLower().StartsWith("s")){ sdp.SessionName = line.Split(new char[]{'='},2)[1].Trim(); } // i Session Information else if(line.ToLower().StartsWith("i")){ sdp.SessionDescription = line.Split(new char[]{'='},2)[1].Trim(); } // u URI else if(line.ToLower().StartsWith("u")){ sdp.Uri = line.Split(new char[]{'='},2)[1].Trim(); } // c Connection Data else if(line.ToLower().StartsWith("c")){ sdp.Connection = SDP_Connection.Parse(line); } // t Timing else if(line.ToLower().StartsWith("t")){ sdp.Times.Add(SDP_Time.Parse(line)); } // a Attributes else if(line.ToLower().StartsWith("a")){ sdp.Attributes.Add(SDP_Attribute.Parse(line)); } line = r.ReadLine().Trim(); } return sdp; }
/// <summary> /// Parses SDP from raw data. /// </summary> /// <param name="data">Raw SDP data.</param> /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception> public static SDP_Message Parse(string data) { if (data == null) { throw new ArgumentNullException("data"); } SDP_Message sdp = new SDP_Message(); System.IO.StringReader r = new System.IO.StringReader(data); string line = r.ReadLine(); //--- Read global fields --------------------------------------------- while (line != null) { line = line.Trim(); // We reached to media descriptions if (line.ToLower().StartsWith("m")) { /* * m= (media name and transport address) * i=* (media title) * c=* (connection information -- optional if included at session level) * b=* (zero or more bandwidth information lines) * k=* (encryption key) * a=* (zero or more media attribute lines) */ SDP_MediaDescription media = SDP_MediaDescription.Parse(line); sdp.m_pMediaDescriptions.Add(media); line = r.ReadLine(); // Pasrse media fields and attributes while (line != null) { line = line.Trim(); // Next media descrition, just stop active media description parsing, // fall through main while, allow next while loop to process it. if (line.ToLower().StartsWith("m")) { break; } // i media title else if (line.ToLower().StartsWith("i")) { media.Information = line.Split(new char[] { '=' }, 2)[1].Trim(); } // c connection information else if (line.ToLower().StartsWith("c")) { media.Connection = SDP_Connection.Parse(line); } // a Attributes else if (line.ToLower().StartsWith("a")) { media.Attributes.Add(SDP_Attribute.Parse(line)); } line = r.ReadLine(); } if (line == null) { break; } else { continue; } } // v Protocol Version else if (line.ToLower().StartsWith("v")) { sdp.Version = line.Split(new char[] { '=' }, 2)[1].Trim(); } // o Origin else if (line.ToLower().StartsWith("o")) { sdp.Origin = SDP_Origin.Parse(line); } // s Session Name else if (line.ToLower().StartsWith("s")) { sdp.SessionName = line.Split(new char[] { '=' }, 2)[1].Trim(); } // i Session Information else if (line.ToLower().StartsWith("i")) { sdp.SessionDescription = line.Split(new char[] { '=' }, 2)[1].Trim(); } // u URI else if (line.ToLower().StartsWith("u")) { sdp.Uri = line.Split(new char[] { '=' }, 2)[1].Trim(); } // c Connection Data else if (line.ToLower().StartsWith("c")) { sdp.Connection = SDP_Connection.Parse(line); } // t Timing else if (line.ToLower().StartsWith("t")) { sdp.Times.Add(SDP_Time.Parse(line)); } // a Attributes else if (line.ToLower().StartsWith("a")) { sdp.Attributes.Add(SDP_Attribute.Parse(line)); } line = r.ReadLine().Trim(); } return(sdp); }
/// <summary> /// Is called when SIP stack has received request. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pStack_RequestReceived(object sender,SIP_RequestReceivedEventArgs e) { try{ #region CANCEL if(e.Request.RequestLine.Method == SIP_Methods.CANCEL){ /* RFC 3261 9.2. If the UAS did not find a matching transaction for the CANCEL according to the procedure above, it SHOULD respond to the CANCEL with a 481 (Call Leg/Transaction Does Not Exist). Regardless of the method of the original request, as long as the CANCEL matched an existing transaction, the UAS answers the CANCEL request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if(trToCancel != null){ trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok,e.Request)); } else{ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist,e.Request)); } } #endregion #region BYE else if(e.Request.RequestLine.Method == SIP_Methods.BYE){ /* RFC 3261 15.1.2. If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ // Currently we match BYE to dialog and it processes it, // so BYE what reaches here doesnt match to any dialog. e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist,e.Request)); } #endregion #region INVITE else if(e.Request.RequestLine.Method == SIP_Methods.INVITE){ #region Incoming call if(e.Dialog == null){ #region Validate incoming call // We don't accept more than 1 call at time. if(m_pIncomingCallUI != null || m_pCall != null){ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x600_Busy_Everywhere,e.Request)); return; } // We don't accept SDP offerless calls. if(e.Request.ContentType == null || e.Request.ContentType.ToLower().IndexOf("application/sdp") == -1){ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x606_Not_Acceptable + ": We don't accpet SDP offerless calls.",e.Request)); return; } SDP_Message sdpOffer = SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data)); // Check if we can accept any media stream. bool canAccept = false; foreach(SDP_MediaDescription media in sdpOffer.MediaDescriptions){ if(CanSupportMedia(media)){ canAccept = true; break; } } if(!canAccept){ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x606_Not_Acceptable,e.Request)); return; } #endregion // Send ringing to remote-party. SIP_Response responseRinging = m_pStack.CreateResponse(SIP_ResponseCodes.x180_Ringing,e.Request,e.Flow); responseRinging.To.Tag = SIP_Utils.CreateTag(); e.ServerTransaction.SendResponse(responseRinging); SIP_Dialog_Invite dialog = (SIP_Dialog_Invite)m_pStack.TransactionLayer.GetOrCreateDialog(e.ServerTransaction,responseRinging); // We need invoke here, otherwise we block SIP stack RequestReceived event while incoming call UI showed. this.BeginInvoke(new MethodInvoker(delegate(){ try{ m_pPlayer.Play(ResManager.GetStream("ringing.wav"),20); // Show incoming call UI. m_pIncomingCallUI = new wfrm_IncomingCall(e.ServerTransaction); // Call accepted. if(m_pIncomingCallUI.ShowDialog(this) == DialogResult.Yes){ RTP_MultimediaSession rtpMultimediaSession = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME()); // Build local SDP template SDP_Message sdpLocal = new SDP_Message(); sdpLocal.Version = "0"; sdpLocal.Origin = new SDP_Origin("-",sdpLocal.GetHashCode(),1,"IN","IP4",System.Net.Dns.GetHostAddresses("")[0].ToString()); sdpLocal.SessionName = "SIP Call"; sdpLocal.Times.Add(new SDP_Time(0,0)); ProcessMediaOffer(dialog,e.ServerTransaction,rtpMultimediaSession,sdpOffer,sdpLocal); // Create call. m_pCall = new SIP_Call(m_pStack,dialog,rtpMultimediaSession,sdpLocal); m_pCall.StateChanged += new EventHandler(m_pCall_StateChanged); m_pCall_StateChanged(m_pCall,new EventArgs()); if(m_IsDebug){ wfrm_RTP_Debug rtpDebug = new wfrm_RTP_Debug(m_pCall.RtpMultimediaSession); rtpDebug.Show(); } } // Call rejected. else{ // Transaction response is sent in call UI. dialog.Terminate(null,false); } m_pIncomingCallUI = null; m_pPlayer.Stop(); } catch(Exception x1){ MessageBox.Show("Error: " + x1.Message,"Error:",MessageBoxButtons.OK,MessageBoxIcon.Error); } })); } #endregion #region Re-INVITE else{ try{ // Remote-party provided SDP offer. if(e.Request.ContentType != null && e.Request.ContentType.ToLower().IndexOf("application/sdp") > -1){ ProcessMediaOffer(m_pCall.Dialog,e.ServerTransaction,m_pCall.RtpMultimediaSession,SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data)),m_pCall.LocalSDP); // We are holding a call. if(m_pToggleOnHold.Text == "Unhold"){ // We don't need to do anything here. } // Remote-party is holding a call. else if(IsRemotePartyHolding(SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data)))){ // We need invoke here, we are running on thread pool thread. this.BeginInvoke(new MethodInvoker(delegate(){ m_pStatusBar.Items["text"].Text = "Remote party holding a call"; })); m_pPlayer.Play(ResManager.GetStream("onhold.wav"),20); } // Call is active. else{ // We need invoke here, we are running on thread pool thread. this.BeginInvoke(new MethodInvoker(delegate(){ m_pStatusBar.Items["text"].Text = "Call established"; })); m_pPlayer.Stop(); } } // Error: Re-INVITE can't be SDP offerless. else{ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Re-INVITE must contain SDP offer.",e.Request)); } } catch(Exception x1){ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x1.Message,e.Request)); } } #endregion } #endregion #region ACK else if(e.Request.RequestLine.Method == SIP_Methods.ACK){ // Abandoned ACK, just skip it. } #endregion #region Other else{ // ACK is response less method. if(e.Request.RequestLine.Method != SIP_Methods.ACK){ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented,e.Request)); } } #endregion } catch{ e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error,e.Request)); } }
/// <summary> /// Default incoming call constructor. /// </summary> /// <param name="ua">Owner UA.</param> /// <param name="invite">INVITE server transaction.</param> /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception> internal SIP_UA_Call(SIP_UA ua,SIP_ServerTransaction invite) { if(ua == null){ throw new ArgumentNullException("ua"); } if(invite == null){ throw new ArgumentNullException("invite"); } m_pUA = ua; m_pInitialInviteTransaction = invite; m_pLocalUri = invite.Request.To.Address.Uri; m_pRemoteUri = invite.Request.From.Address.Uri; m_pInitialInviteTransaction.Canceled += new EventHandler(delegate(object sender,EventArgs e){ // If transaction canceled, terminate call. SetState(SIP_UA_CallState.Terminated); }); // Parse SDP if INVITE contains SDP. // RFC 3261 13.2.1. INVITE may be offerless, we must thne send offer and remote party sends sdp in ACK. if(invite.Request.ContentType != null && invite.Request.ContentType.ToLower().IndexOf("application/sdp") > -1){ m_pRemoteSDP = SDP_Message.Parse(Encoding.UTF8.GetString(invite.Request.Data)); } m_pTags = new Dictionary<string,object>(); m_State = SIP_UA_CallState.WaitingToAccept; }
/// <summary> /// Accepts call. /// </summary> /// <param name="sdp">Media answer.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>sdp</b> is null reference.</exception> public void Accept(SDP_Message sdp) { if(m_State == SIP_UA_CallState.Disposed){ throw new ObjectDisposedException(this.GetType().Name); } if(m_State != SIP_UA_CallState.WaitingToAccept){ throw new InvalidOperationException("Accept method can be called only in 'SIP_UA_CallState.WaitingToAccept' state."); } if(sdp == null){ throw new ArgumentNullException("sdp"); } m_pLocalSDP = sdp; // TODO: We must add Contact header and SDP to response. SIP_Response response = m_pUA.Stack.CreateResponse(SIP_ResponseCodes.x200_Ok,m_pInitialInviteTransaction.Request,m_pInitialInviteTransaction.Flow); response.ContentType = "application/sdp"; response.Data = sdp.ToByte(); m_pInitialInviteTransaction.SendResponse(response); SetState(SIP_UA_CallState.Active); m_pDialog = m_pUA.Stack.TransactionLayer.GetOrCreateDialog(m_pInitialInviteTransaction,response); m_pDialog.StateChanged += new EventHandler(m_pDialog_StateChanged); }
/// <summary> /// Initializes call from Calling state to active.. /// </summary> /// <param name="dialog">SIP dialog.</param> /// <param name="localSDP">Local SDP.</param> /// <exception cref="ArgumentNullException">Is raised when <b>dialog</b> or <b>localSDP</b> is null reference.</exception> internal void InitCalling(SIP_Dialog dialog, SDP_Message localSDP) { if (dialog == null) { throw new ArgumentNullException("dialog"); } if (localSDP == null) { throw new ArgumentNullException("localSDP"); } m_pDialog = (SIP_Dialog_Invite)dialog; m_pFlow = dialog.Flow; m_pLocalSDP = localSDP; m_StartTime = DateTime.Now; dialog.StateChanged += new EventHandler(m_pDialog_StateChanged); SetState(SIP_CallState.Active); // Start ping timer. m_pKeepAliveTimer = new TimerEx(40000); m_pKeepAliveTimer.Elapsed += new System.Timers.ElapsedEventHandler(m_pKeepAliveTimer_Elapsed); m_pKeepAliveTimer.Enabled = true; }
private void ProcessMediaAnswer(SIP_Call call, SDP_Message offer, SDP_Message answer) { if (call == null) { throw new ArgumentNullException("call"); } if (offer == null) { throw new ArgumentNullException("offer"); } if (answer == null) { throw new ArgumentNullException("answer"); } try { #region SDP basic validation // Version field must exist. if (offer.Version == null) { call.Terminate("Invalid SDP answer: Required 'v'(Protocol Version) field is missing."); return; } // Origin field must exist. if (offer.Origin == null) { call.Terminate("Invalid SDP answer: Required 'o'(Origin) field is missing."); return; } // Session Name field. // Check That global 'c' connection attribute exists or otherwise each enabled media stream must contain one. if (offer.Connection == null) { for (int i = 0; i < offer.MediaDescriptions.Count; i++) { if (offer.MediaDescriptions[i].Connection == null) { call.Terminate("Invalid SDP answer: Global or per media stream no: " + i + " 'c'(Connection) attribute is missing."); return; } } } // Check media streams count. if (offer.MediaDescriptions.Count != answer.MediaDescriptions.Count) { call.Terminate("Invalid SDP answer, media descriptions count in answer must be equal to count in media offer (RFC 3264 6.)."); return; } #endregion // Process media streams info. for (int i = 0; i < offer.MediaDescriptions.Count; i++) { SDP_MediaDescription offerMedia = offer.MediaDescriptions[i]; SDP_MediaDescription answerMedia = answer.MediaDescriptions[i]; // Remote-party disabled this stream. if (answerMedia.Port == 0) { #region Cleanup active RTP stream and it's resources, if it exists // Dispose existing RTP session. if (offerMedia.Tags.ContainsKey("rtp_session")) { ((RTP_Session)offerMedia.Tags["rtp_session"]).Dispose(); offerMedia.Tags.Remove("rtp_session"); } // Release UPnPports if any. if (offerMedia.Tags.ContainsKey("upnp_rtp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)offerMedia.Tags["upnp_rtp_map"]); } catch { } offerMedia.Tags.Remove("upnp_rtp_map"); } if (offerMedia.Tags.ContainsKey("upnp_rtcp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)offerMedia.Tags["upnp_rtcp_map"]); } catch { } offerMedia.Tags.Remove("upnp_rtcp_map"); } #endregion } // Remote-party accepted stream. else { Dictionary<int, AudioCodec> audioCodecs = (Dictionary<int, AudioCodec>)offerMedia.Tags["audio_codecs"]; #region Validate stream-mode disabled,inactive,sendonly,recvonly /* RFC 3264 6.1. If a stream is offered as sendonly, the corresponding stream MUST be marked as recvonly or inactive in the answer. If a media stream is listed as recvonly in the offer, the answer MUST be marked as sendonly or inactive in the answer. If an offered media stream is listed as sendrecv (or if there is no direction attribute at the media or session level, in which case the stream is sendrecv by default), the corresponding stream in the answer MAY be marked as sendonly, recvonly, sendrecv, or inactive. If an offered media stream is listed as inactive, it MUST be marked as inactive in the answer. */ // If we disabled this stream in offer and answer enables it (no allowed), terminate call. if (offerMedia.Port == 0) { call.Terminate("Invalid SDP answer, you may not enable sdp-offer disabled stream no: " + i + " (RFC 3264 6.)."); return; } RTP_StreamMode offerStreamMode = GetRtpStreamMode(offer, offerMedia); RTP_StreamMode answerStreamMode = GetRtpStreamMode(answer, answerMedia); if (offerStreamMode == RTP_StreamMode.Send && answerStreamMode != RTP_StreamMode.Receive) { call.Terminate("Invalid SDP answer, sdp stream no: " + i + " stream-mode must be 'recvonly' (RFC 3264 6.)."); return; } if (offerStreamMode == RTP_StreamMode.Receive && answerStreamMode != RTP_StreamMode.Send) { call.Terminate("Invalid SDP answer, sdp stream no: " + i + " stream-mode must be 'sendonly' (RFC 3264 6.)."); return; } if (offerStreamMode == RTP_StreamMode.Inactive && answerStreamMode != RTP_StreamMode.Inactive) { call.Terminate("Invalid SDP answer, sdp stream no: " + i + " stream-mode must be 'inactive' (RFC 3264 6.)."); return; } #endregion #region Create/modify RTP session RTP_Session rtpSession = (RTP_Session)offerMedia.Tags["rtp_session"]; rtpSession.Payload = Convert.ToInt32(answerMedia.MediaFormats[0]); rtpSession.StreamMode = (answerStreamMode == RTP_StreamMode.Inactive ? RTP_StreamMode.Inactive : offerStreamMode); rtpSession.RemoveTargets(); if (GetSdpHost(answer, answerMedia) != "0.0.0.0") { rtpSession.AddTarget(GetRtpTarget(answer, answerMedia)); } rtpSession.Start(); #endregion #region Create/modify audio-in source if (!offerMedia.Tags.ContainsKey("rtp_audio_in")) { AudioIn_RTP rtpAudioIn = new AudioIn_RTP(m_pAudioInDevice, 20, audioCodecs, rtpSession.CreateSendStream()); rtpAudioIn.Start(); offerMedia.Tags.Add("rtp_audio_in", rtpAudioIn); } #endregion } } call.LocalSDP = offer; call.RemoteSDP = answer; } catch (Exception x) { call.Terminate("Error processing SDP answer: " + x.Message); } }
private void Call(SIP_t_NameAddress from, SIP_t_NameAddress to) { if (from == null) { throw new ArgumentNullException("from"); } if (to == null) { throw new ArgumentNullException("to"); } #region Setup RTP session RTP_MultimediaSession rtpMultimediaSession = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME()); RTP_Session rtpSession = CreateRtpSession(rtpMultimediaSession); // Port search failed. if (rtpSession == null) { throw new Exception("Calling not possible, RTP session failed to allocate IP end points."); } if (m_IsDebug) { wfrm_RTP_Debug rtpDebug = new wfrm_RTP_Debug(rtpMultimediaSession); rtpDebug.Show(); } #endregion #region Create SDP offer SDP_Message sdpOffer = new SDP_Message(); sdpOffer.Version = "0"; sdpOffer.Origin = new SDP_Origin("-", sdpOffer.GetHashCode(), 1, "IN", "IP4", System.Net.Dns.GetHostAddresses("")[0].ToString()); sdpOffer.SessionName = "SIP Call"; sdpOffer.Times.Add(new SDP_Time(0, 0)); #region Add 1 audio stream SDP_MediaDescription mediaStream = new SDP_MediaDescription(SDP_MediaTypes.audio, 0, 1, "RTP/AVP", null); rtpSession.NewReceiveStream += delegate(object s, RTP_ReceiveStreamEventArgs e) { AudioOut_RTP audioOut = new AudioOut_RTP(m_pAudioOutDevice, e.Stream, m_pAudioCodecs); audioOut.Start(); mediaStream.Tags["rtp_audio_out"] = audioOut; }; if (!HandleNAT(mediaStream, rtpSession)) { throw new Exception("Calling not possible, because of NAT or firewall restrictions."); } foreach (KeyValuePair<int, AudioCodec> entry in m_pAudioCodecs) { mediaStream.Attributes.Add(new SDP_Attribute("rtpmap", entry.Key + " " + entry.Value.Name + "/" + entry.Value.CompressedAudioFormat.SamplesPerSecond)); mediaStream.MediaFormats.Add(entry.Key.ToString()); } mediaStream.Attributes.Add(new SDP_Attribute("ptime", "20")); mediaStream.Attributes.Add(new SDP_Attribute("sendrecv", "")); mediaStream.Tags["rtp_session"] = rtpSession; mediaStream.Tags["audio_codecs"] = m_pAudioCodecs; sdpOffer.MediaDescriptions.Add(mediaStream); #endregion #endregion // Create INVITE request. SIP_Request invite = m_pStack.CreateRequest(SIP_Methods.INVITE, to, from); invite.ContentType = "application/sdp"; invite.Data = sdpOffer.ToByte(); SIP_RequestSender sender = m_pStack.CreateRequestSender(invite); // Create call. m_pCall = new SIP_Call(m_pStack, sender, rtpMultimediaSession); m_pCall.LocalSDP = sdpOffer; m_pCall.StateChanged += new EventHandler(m_pCall_StateChanged); bool finalResponseSeen = false; List<SIP_Dialog_Invite> earlyDialogs = new List<SIP_Dialog_Invite>(); sender.ResponseReceived += delegate(object s, SIP_ResponseReceivedEventArgs e) { // Skip 2xx retransmited response. if (finalResponseSeen) { return; } if (e.Response.StatusCode >= 200) { finalResponseSeen = true; } try { #region Provisional if (e.Response.StatusCodeType == SIP_StatusCodeType.Provisional) { /* RFC 3261 13.2.2.1. Zero, one or multiple provisional responses may arrive before one or more final responses are received. Provisional responses for an INVITE request can create "early dialogs". If a provisional response has a tag in the To field, and if the dialog ID of the response does not match an existing dialog, one is constructed using the procedures defined in Section 12.1.2. */ if (e.Response.StatusCode > 100 && e.Response.To.Tag != null) { earlyDialogs.Add((SIP_Dialog_Invite)e.GetOrCreateDialog); } // 180_Ringing. if (e.Response.StatusCode == 180) { //m_pPlayer.Play(ResManager.GetStream("ringing.wav"), 10); // We need BeginInvoke here, otherwise we block client transaction. m_pStatusBar.BeginInvoke(new MethodInvoker(delegate() { m_pStatusBar.Items[0].Text = "Ringing"; })); } } #endregion #region Success else if (e.Response.StatusCodeType == SIP_StatusCodeType.Success) { SIP_Dialog dialog = e.GetOrCreateDialog; /* Exit all all other dialogs created by this call (due to forking). That is not defined in RFC but, since UAC can send BYE to early and confirmed dialogs, all this is 100% valid. */ foreach (SIP_Dialog_Invite d in earlyDialogs.ToArray()) { if (!d.Equals(dialog)) { d.Terminate("Another forking leg accepted.", true); } } m_pCall.InitCalling(dialog, sdpOffer); // Remote-party provided SDP. if (e.Response.ContentType != null && e.Response.ContentType.ToLower().IndexOf("application/sdp") > -1) { try { // SDP offer. We sent offerless INVITE, we need to send SDP answer in ACK request.' if (e.ClientTransaction.Request.ContentType == null || e.ClientTransaction.Request.ContentType.ToLower().IndexOf("application/sdp") == -1) { // Currently we never do it, so it never happens. This is place holder, if we ever support it. } // SDP answer to our offer. else { // This method takes care of ACK sending and 2xx response retransmission ACK sending. HandleAck(m_pCall.Dialog, e.ClientTransaction); ProcessMediaAnswer(m_pCall, m_pCall.LocalSDP, SDP_Message.Parse(Encoding.UTF8.GetString(e.Response.Data))); } } catch { m_pCall.Terminate("SDP answer parsing/processing failed."); } } else { // If we provided SDP offer, there must be SDP answer. if (e.ClientTransaction.Request.ContentType != null && e.ClientTransaction.Request.ContentType.ToLower().IndexOf("application/sdp") > -1) { m_pCall.Terminate("Invalid 2xx response, required SDP answer is missing."); } } // Stop ringing. m_pPlayer.Stop(); } #endregion #region Failure else { /* RFC 3261 13.2.2.3. All early dialogs are considered terminated upon reception of the non-2xx final response. */ foreach (SIP_Dialog_Invite dialog in earlyDialogs.ToArray()) { dialog.Terminate("All early dialogs are considered terminated upon reception of the non-2xx final response. (RFC 3261 13.2.2.3)", false); } // We need BeginInvoke here, otherwise we block client transaction while message box open. if (m_pCall.State != SIP_CallState.Terminating) { this.BeginInvoke(new MethodInvoker(delegate() { m_pConnect.Image = global::PowerSDR.Properties.Resources.call; connected = false; MessageBox.Show("Calling failed: " + e.Response.StatusCode_ReasonPhrase, "Error:", MessageBoxButtons.OK, MessageBoxIcon.Error); })); } // We need BeginInvoke here, otherwise we block client transaction. m_pStatusBar.BeginInvoke(new MethodInvoker(delegate() { m_pStatusBar.Items[0].Text = ""; })); // Stop calling or ringing. m_pPlayer.Stop(); // Terminate call. m_pCall.Terminate("Remote party rejected a call.", false); } #endregion } catch (Exception x) { // We need BeginInvoke here, otherwise we block client transaction while message box open. this.BeginInvoke(new MethodInvoker(delegate() { MessageBox.Show("Error: " + x.Message, "Error:", MessageBoxButtons.OK, MessageBoxIcon.Error); })); } }; m_pStatusBar.Items[0].Text = "Calling"; m_pStatusBar.Items[1].Text = "00:00:00"; //m_pPlayer.Play(ResManager.GetStream("calling.wav"), 10); // Start calling. sender.Start(); }
/// <summary> /// This method is called when initial INVITE sender got response. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pInitialInviteSender_ResponseReceived(object sender,SIP_ResponseReceivedEventArgs e) { try{ lock(m_pLock){ // If remote party provided SDP, parse it. if(e.Response.ContentType != null && e.Response.ContentType.ToLower().IndexOf("application/sdp") > -1){ m_pRemoteSDP = SDP_Message.Parse(Encoding.UTF8.GetString(e.Response.Data)); // TODO: If parsing failed, end call. } if(e.Response.StatusCodeType == SIP_StatusCodeType.Provisional){ if(e.Response.StatusCode == 180){ SetState(SIP_UA_CallState.Ringing); } else if(e.Response.StatusCode == 182){ SetState(SIP_UA_CallState.Queued); } // We don't care other status responses. /* RFC 3261 13.2.2.1. Zero, one or multiple provisional responses may arrive before one or more final responses are received. Provisional responses for an INVITE request can create "early dialogs". If a provisional response has a tag in the To field, and if the dialog ID of the response does not match an existing dialog, one is constructed using the procedures defined in Section 12.1.2. */ if(e.Response.StatusCode > 100 && e.Response.To.Tag != null){ m_pEarlyDialogs.Add((SIP_Dialog_Invite)m_pUA.Stack.TransactionLayer.GetOrCreateDialog(e.ClientTransaction,e.Response)); } } else if(e.Response.StatusCodeType == SIP_StatusCodeType.Success){ m_StartTime = DateTime.Now; SetState(SIP_UA_CallState.Active); m_pDialog = m_pUA.Stack.TransactionLayer.GetOrCreateDialog(e.ClientTransaction,e.Response); m_pDialog.StateChanged += new EventHandler(m_pDialog_StateChanged); /* Exit all all other dialogs created by this call (due to forking). That is not defined in RFC but, since UAC can send BYE to early and confirmed dialogs, because of this all 100% valid. */ foreach(SIP_Dialog_Invite dialog in m_pEarlyDialogs.ToArray()){ if(!m_pDialog.Equals(dialog)){ dialog.Terminate("Another forking leg accepted.",true); } } } else{ /* RFC 3261 13.2.2.3. All early dialogs are considered terminated upon reception of the non-2xx final response. */ foreach(SIP_Dialog_Invite dialog in m_pEarlyDialogs.ToArray()){ dialog.Terminate("All early dialogs are considered terminated upon reception of the non-2xx final response. (RFC 3261 13.2.2.3)",false); } m_pEarlyDialogs.Clear(); Error(); SetState(SIP_UA_CallState.Terminated); } } } catch(Exception x){ m_pUA.Stack.OnError(x); } }