Example #1
0
        /// <summary>
        /// Starts calling.
        /// </summary>
        /// <param name="from">From address.</param>
        /// <param name="to">To address.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>from</b> or <b>to</b> is null reference.</exception>
        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["text"].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_pCall_HangUp.Image = ResManager.GetIcon("call.ico",new Size(24,24)).ToBitmap();
                                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["text"].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["text"].Text = "Calling";
            m_pStatusBar.Items["duration"].Text = "00:00:00";
            m_pPlayer.Play(ResManager.GetStream("calling.wav"),10);

            // Start calling.
            sender.Start();
        }              
Example #2
0
        /// <summary>
        /// Processes media answer.
        /// </summary>
        /// <param name="call">SIP call.</param>
        /// <param name="offer">SDP media offer.</param>
        /// <param name="answer">SDP remote-party meida answer.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>call</b>,<b>offer</b> or <b>answer</b> is null reference.</exception>
        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);
            }
        }