public void PlaceCall(string destination, SDP sdp)
        {
            m_destination = destination;
            m_sdp = sdp;

            XElement descriptionElement = SDPToJingle.GetDescription(m_sdp);

            m_descriptionID = Crypto.GetRandomString(6);

            XElement callElement = new XElement(JabberClientNS + "iq",
                                    new XAttribute("from", m_jid),
                                    new XAttribute("id", m_descriptionID),
                                    new XAttribute("to", m_destination),
                                    new XAttribute("type", "set"),
                                    new XElement(m_sessionNS + "session",
                                        new XAttribute("type", "initiate"),
                                        new XAttribute("id", m_sessionID),
                                        new XAttribute("initiator", m_jid),
                                        descriptionElement
                                        //new XElement(m_transportNS + "transport")
                                        ));

            logger.Debug("XMPPPhoneSession sending iq with session description.");
            m_xmppStream.WriteElement(callElement, OnIQResponse);
        }
示例#2
0
        /// <summary>
        /// Updates the session after receiving the remote SDP.
        /// At this point check that the codecs match. We currently only support:
        ///  - Audio: PCMU,
        ///  - Video: VP8.
        /// If they are not available there's no point carrying on.
        /// </summary>
        /// <param name="remoteSdp">The answer/offer SDP from the remote party.</param>
        public void OnSdpAnswer(SDP remoteSdp)
        {
            // Check remote party audio is acceptable.
            if (_supportedAudioFormats?.Count() > 0)
            {
                var remoteAudioOffer = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault();
                if (remoteAudioOffer?.MediaFormats.Count() == 0)
                {
                    logger.LogWarning("No audio formats were available in the remote party's SDP.");
                    Close("No audio codecs offered.");
                }
                else if (remoteAudioOffer.MediaFormats.Select(x => x.FormatCodec).Union(_supportedAudioFormats.Select(y => y.FormatCodec)).Count() == 0)
                {
                    logger.LogWarning("No matching audio codec was available.");
                    Close("No matching audio codec.");
                }
            }

            // Check remote party video is acceptable.
            if (_supportedVideoFormats?.Count() > 0)
            {
                var remoteVideoOffer = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault();
                if (remoteVideoOffer?.MediaFormats.Count() == 0)
                {
                    logger.LogWarning("No video formats were available in the remote party's SDP.");
                    Close("No video codecs offered.");
                }
                else if (remoteVideoOffer.MediaFormats.Select(x => x.FormatCodec).Union(_supportedVideoFormats.Select(y => y.FormatCodec)).Count() == 0)
                {
                    logger.LogWarning("No matching video codec was available.");
                    Close("No matching video codec.");
                }

                // Since we only currently support VP8 there's only a single remote payload ID that can be
                // associated with the video stream.
                var remoteVP8MediaFormat = remoteVideoOffer.MediaFormats.Where(x => x.FormatCodec == SDPMediaFormatsEnum.VP8).Single();
                RtpSession.AddStream(SDPMediaTypesEnum.video, VP8_PAYLOAD_TYPE_ID, new List <int> {
                    Convert.ToInt32(remoteVP8MediaFormat.FormatID)
                });
            }

            SdpSessionID      = remoteSdp.SessionId;
            RemoteIceUser     = remoteSdp.IceUfrag;
            RemoteIcePassword = remoteSdp.IcePwd;

            // All browsers seem to have gone to trickling ICE candidates now but just
            // in case one or more are given we can start the STUN dance immediately.
            if (remoteSdp.IceCandidates != null)
            {
                foreach (var iceCandidate in remoteSdp.IceCandidates)
                {
                    AppendRemoteIceCandidate(iceCandidate);
                }
            }
        }
示例#3
0
        /// <summary>
        /// Convenience overload to suit SIP/VoIP callers.
        /// TODO: Consolidate with createAnswer.
        /// </summary>
        /// <param name="connectionAddress">Not used.</param>
        /// <returns>An SDP payload to answer an offer from the remote party.</returns>
        public override SDP CreateAnswer(IPAddress connectionAddress)
        {
            var result = createAnswer(null);

            if (result?.sdp != null)
            {
                return(SDP.ParseSDPDescription(result.sdp));
            }

            return(null);
        }
示例#4
0
        /// <summary>
        /// Generates the base SDP for an offer or answer. The SDP will then be tailored depending
        /// on whether it's being used in an offer or an answer.
        /// </summary>
        /// <param name="audioCapabilities">Optional. The audio formats to support in the SDP. This list can differ from
        /// the local audio track if an answer is being generated and only mutually supported formats are being
        /// used.</param>
        /// <param name="videoCapabilities">Optional. The video formats to support in the SDP. This list can differ from
        /// the local video track if an answer is being generated and only mutually supported formats are being
        /// used.</param>
        /// <remarks>
        /// From https://tools.ietf.org/html/draft-ietf-mmusic-ice-sip-sdp-39#section-4.2.5:
        ///   "The transport address from the peer for the default destination
        ///   is set to IPv4/IPv6 address values "0.0.0.0"/"::" and port value
        ///   of "9".  This MUST NOT be considered as a ICE failure by the peer
        ///   agent and the ICE processing MUST continue as usual."
        /// </remarks>
        private SDP createBaseSdp(List <MediaStreamTrack> tracks, List <SDPMediaFormat> audioCapabilities, List <SDPMediaFormat> videoCapabilities)
        {
            SDP offerSdp = new SDP(IPAddress.Loopback);

            offerSdp.SessionId = LocalSdpSessionID;

            bool iceCandidatesAdded = false;

            // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed
            // on a single RTP socket.
            offerSdp.Group           = BUNDLE_ATTRIBUTE;
            offerSdp.DtlsFingerprint = _currentCertificate.getFingerprints().First().ToString();

            // Media announcements must be in the same order in the offer and answer.
            foreach (var track in tracks.OrderBy(x => x.MLineIndex))
            {
                offerSdp.Group += $" {track.MID}";

                SDPMediaAnnouncement announcement = new SDPMediaAnnouncement(
                    track.Kind,
                    SDP.IGNORE_RTP_PORT_NUMBER,
                    (track.Kind == SDPMediaTypesEnum.video) ? videoCapabilities : audioCapabilities);

                announcement.Transport  = RTP_MEDIA_PROFILE;
                announcement.Connection = new SDPConnectionInformation(IPAddress.Any);
                announcement.AddExtra(RTCP_MUX_ATTRIBUTE);
                announcement.AddExtra(RTCP_ATTRIBUTE);
                announcement.MediaStreamStatus = track.StreamStatus;
                announcement.MediaID           = track.MID;

                announcement.IceUfrag        = _rtpIceChannel.LocalIceUser;
                announcement.IcePwd          = _rtpIceChannel.LocalIcePassword;
                announcement.IceOptions      = ICE_OPTIONS;
                announcement.DtlsFingerprint = offerSdp.DtlsFingerprint;

                if (iceCandidatesAdded == false && _rtpIceChannel.Candidates?.Count > 0)
                {
                    announcement.IceCandidates = new List <string>();

                    // Add ICE candidates.
                    foreach (var iceCandidate in _rtpIceChannel.Candidates)
                    {
                        announcement.IceCandidates.Add(iceCandidate.ToString());
                    }

                    iceCandidatesAdded = true;
                }

                offerSdp.Media.Add(announcement);
            }

            return(offerSdp);
        }
示例#5
0
        /// <summary>
        /// Gets the a basic Session Description Protocol object that describes this RTP session.
        /// </summary>
        /// <param name="localAddress">The RTP socket we will be sending from. Note this can't be IPAddress.Any as
        /// it's getting sent to the callee. An IP address of 0.0.0.0 or [::0] will typically be interpreted as
        /// "don't send me any RTP".</param>
        /// <returns>An Session Description Protocol object that can be sent to a remote callee.</returns>
        public SDP GetSDP(IPAddress localAddress)
        {
            var sdp = new SDP(localAddress)
            {
                SessionId   = Crypto.GetRandomInt(5).ToString(),
                SessionName = "sipsorcery",
                Timing      = "0 0",
                Connection  = new SDPConnectionInformation(localAddress),
            };

            sdp.Media.Add(MediaAnnouncement);

            return(sdp);
        }
        private void Answered(SDP sdp)
        {
            Console.WriteLine("XMPP client call answered.");

            IsUACAnswered = true;

            SIPResponse okResponse = new SIPResponse(SIPResponseStatusCodesEnum.Ok, "Ok", new SIPEndPoint(new IPEndPoint(IPAddress.Loopback, 0)));
            okResponse.Header.ContentType = SDP.SDP_MIME_CONTENTTYPE;
            okResponse.Body = sdp.ToString();

            SIPDialogue = new SIPDialogue(null, null, null, null, -1, null, null, null, Guid.NewGuid(), Owner, AdminMemberId, null, sdp.ToString());
            SIPDialogue.CallDurationLimit = CallDescriptor.CallDurationLimit;

            CallAnswered(this, okResponse);
        }
示例#7
0
        /// <summary>
        /// Updates the session after receiving the remote SDP.
        /// At this point check that the codecs match. We currently only support:
        ///  - Audio: PCMU,
        ///  - Video: VP8.
        /// If they are not available there's no point carrying on.
        /// </summary>
        /// <param name="sdpType">Whether the remote SDP is an offer or an answer.</param>
        /// <param name="remoteSdp">The answer/offer SDP from the remote party.</param>
        public void setRemoteDescription(SdpType sdpType, SDP remoteSdp)
        {
            var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault();

            if (audioAnnounce != null)
            {
                RtpSession.AddTrack(audioAnnounce.MediaID, SDPMediaTypesEnum.audio, true, audioAnnounce.MediaFormats);
            }

            var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault();

            if (videoAnnounce != null)
            {
                RtpSession.AddTrack(videoAnnounce.MediaID, SDPMediaTypesEnum.video, true, videoAnnounce.MediaFormats);
            }

            SdpSessionID      = remoteSdp.SessionId;
            RemoteIceUser     = remoteSdp.IceUfrag ?? remoteSdp.Media.First().IceUfrag;
            RemoteIcePassword = remoteSdp.IcePwd ?? remoteSdp.Media.First().IcePwd;

            // All browsers seem to have gone to trickling ICE candidates now but just
            // in case one or more are given we can start the STUN dance immediately.
            if (remoteSdp.IceCandidates != null)
            {
                foreach (var iceCandidate in remoteSdp.IceCandidates)
                {
                    AppendRemoteIceCandidate(iceCandidate);
                }
            }

            foreach (var media in remoteSdp.Media)
            {
                if (media.IceCandidates != null)
                {
                    foreach (var iceCandidate in media.IceCandidates)
                    {
                        AppendRemoteIceCandidate(iceCandidate);
                    }
                }
            }

            m_remoteSDP = remoteSdp;
        }
        public static XElement GetDescription(SDP sdp)
        {
            XElement descriptionElement = new XElement(m_phoneNS + "description");

            foreach (SDPMediaFormat mediaFormat in sdp.Media[0].MediaFormats)
            {
                 XElement payloadElement = new XElement(m_phoneNS + "payload-type",
                    new XAttribute("id", mediaFormat.FormatID),
                    new XAttribute("name", mediaFormat.Name));

                 if (mediaFormat.ClockRate != 0)
                 {
                     payloadElement.Add(new XAttribute("clockrate", mediaFormat.ClockRate));
                 }

                descriptionElement.Add(payloadElement);
            }

            return descriptionElement;
        }
示例#9
0
        /// <summary>
        /// Sets the local SDP.
        /// </summary>
        /// <remarks>
        /// As specified in https://www.w3.org/TR/webrtc/#dom-peerconnection-setlocaldescription.
        /// </remarks>
        /// <param name="description">Optional. The session description to set as
        /// local description. If not supplied then an offer or answer will be created as required.
        /// </param>
        public Task setLocalDescription(RTCSessionDescriptionInit init)
        {
            RTCSessionDescription description = new RTCSessionDescription {
                type = init.type, sdp = SDP.ParseSDPDescription(init.sdp)
            };

            localDescription = description;

            if (init.type == RTCSdpType.offer)
            {
                _rtpIceChannel.IsController = true;
            }

            // This is the point the ICE session potentially starts contacting STUN and TURN servers.
            //IceSession.StartGathering();

            signalingState = RTCSignalingState.have_local_offer;
            onsignalingstatechange?.Invoke();

            return(Task.CompletedTask);
        }
示例#10
0
        /// <param name="rtpListenAddress">The local IP address to establish the RTP listener socket on.</param>
        /// <param name="sdpAdvertiseAddress">The public IP address to put into the SDP sent back to the caller.</param>
        /// <param name="request">The INVITE request that instigated the RTP diagnostics job.</param>
        public RTPDiagnosticsJob(IPAddress rtpListenAddress, IPAddress sdpAdvertiseAddress, SIPServerUserAgent uas, SIPRequest request)
        {
            m_request = request;
            m_remoteSDP = SDP.ParseSDPDescription(request.Body);
            RemoteRTPEndPoint = new IPEndPoint(IPAddress.Parse(m_remoteSDP.Connection.ConnectionAddress), m_remoteSDP.Media[0].Port);
            UAS = uas;
            //m_rawSourceStream = new RawSourceWaveStream(m_outStream, WaveFormat.CreateMuLawFormat(8000, 1));
            //m_waveFileWriter = new WaveFileWriter("out.wav", new WaveFormat(8000, 16, 1));
            m_waveFileWriter = new WaveFileWriter("out.wav", new WaveFormat(8000, 16, 1));
            //m_outPCMStream = WaveFormatConversionStream.CreatePcmStream(m_rawSourceStream);
            //m_rawRTPPayloadWriter = new StreamWriter("out.rtp");
            //m_rawRTPPayloadReader = new StreamReader("in.rtp");
            //IPEndPoint rtpListenEndPoint = null;
            IPEndPoint rtpListenEndPoint = null;
            NetServices.CreateRandomUDPListener(rtpListenAddress, RTP_PORTRANGE_START, RTP_PORTRANGE_END, m_inUsePorts, out rtpListenEndPoint);
            RTPListenEndPoint = rtpListenEndPoint;
            m_inUsePorts.Add(rtpListenEndPoint.Port);
            //RTPListenEndPoint = new IPEndPoint(rtpListenAddress, RTP_PORTRANGE_START);
            m_rtpChannel = new RTPChannel(RTPListenEndPoint);
            m_rtpChannel.SampleReceived += SampleReceived;
            ThreadPool.QueueUserWorkItem(delegate { GetAudioSamples(); });

            LocalSDP = new SDP()
            {
                SessionId = Crypto.GetRandomString(6),
                Address = sdpAdvertiseAddress.ToString(),
                SessionName = "sipsorcery",
                Timing = "0 0",
                Connection = new SDPConnectionInformation(sdpAdvertiseAddress.ToString()),
                Media = new List<SDPMediaAnnouncement>()
                {
                    new SDPMediaAnnouncement()
                    {
                        Media = SDPMediaTypesEnum.audio,
                        Port = RTPListenEndPoint.Port,
                        MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat((int)SDPMediaFormatsEnum.PCMU) }
                    }
                }
            };
        }
示例#11
0
        /// <summary>
        /// Sets the remote SDP offer for this RTP session. It contains required information about payload ID's
        /// for media formats and RTP events.
        /// </summary>
        /// <param name="sdp">The SDP from the remote call party.</param>
        public void SetRemoteSDP(SDP sdp)
        {
            RemoteSDP = sdp;

            foreach (var announcement in sdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio))
            {
                foreach (var mediaFormat in announcement.MediaFormats)
                {
                    if (mediaFormat.FormatAttribute?.StartsWith(TELEPHONE_EVENT_ATTRIBUTE) == true)
                    {
                        if (!int.TryParse(mediaFormat.FormatID, out m_remoteRtpEventPayloadID))
                        {
                            logger.LogWarning("The media format on the telpehone event attribute was not a valid integer.");
                        }
                        break;
                    }
                }
            }

            DestinationEndPoint = sdp.GetSDPRTPEndPoint();
            RtcpSession.ControlDestinationEndPoint = new IPEndPoint(DestinationEndPoint.Address, DestinationEndPoint.Port + 1);
        }
示例#12
0
        public static XElement GetCandidates(SDP sdp)
        {
            string iceUfrag = (sdp.IceUfrag != null) ? sdp.IceUfrag : String.Empty;
            string icePwd = (sdp.IcePwd != null) ? sdp.IcePwd : String.Empty;

            string sdpIPAddress = sdp.Connection.ConnectionAddress;
            int sdpPort = sdp.Media[0].Port;
            string candidateID = Crypto.GetRandomString(6);

            XElement candidateElement = new XElement(m_sessionNS + "candidate",
                                            new XAttribute("name", "rtp"),
                                            new XAttribute("address", sdpIPAddress),
                                            new XAttribute("port", sdpPort),
                                            new XAttribute("username", iceUfrag),
                                            new XAttribute("password", icePwd),
                                            new XAttribute("preference", "1.0"),
                                            new XAttribute("protocol", "udp"),
                                            new XAttribute("type", "local"),
                                            new XAttribute("network", "0"),
                                            new XAttribute("generation", "0"));

            return candidateElement;
        }
示例#13
0
        /// <summary>
        /// A convenience method to get the RTP end point for single audio offer SDP payloads.
        /// </summary>
        /// <param name="sdpMessage">A string representing the SDP payload.</param>
        /// <returns>The RTP end point for the first media end point.</returns>
        public static IPEndPoint GetSDPRTPEndPoint(string sdpMessage)
        {
            // Process the SDP payload.
            //Match portMatch = Regex.Match(sdpMessage, @"m=audio (?<port>\d+)", RegexOptions.Singleline);
            //if (portMatch.Success)
            //{
            //    int rtpServerPort = Convert.ToInt32(portMatch.Result("${port}"));

            //    Match serverMatch = Regex.Match(sdpMessage, @"c=IN IP4 (?<ipaddress>(\d+\.){3}\d+)", RegexOptions.Singleline);
            //    if (serverMatch.Success)
            //    {
            //        string rtpServerAddress = serverMatch.Result("${ipaddress}");

            //        if(IPAddress.TryParse(rtpServerAddress, out var ipAddress))
            //        {
            //            IPEndPoint serverEndPoint = new IPEndPoint(ipAddress, rtpServerPort);
            //            return serverEndPoint;
            //        }
            //    }
            //}

            SDP sdp = SDP.ParseSDPDescription(sdpMessage);

            // Find first media offer.
            var sessionConnection = sdp?.Connection;
            var firstMediaOffer   = sdp?.Media.FirstOrDefault();

            if (sessionConnection != null && firstMediaOffer != null)
            {
                return(new IPEndPoint(IPAddress.Parse(sessionConnection.ConnectionAddress), firstMediaOffer.Port));
            }
            else
            {
                return(null);
            }
        }
示例#14
0
        /// <summary>
        /// Gets an SDP packet that can be used by VoIP clients to negotiate an audio connection. The SDP will only
        /// offer PCMU since that's all I've gotten around to handling.
        /// </summary>
        /// <param name="usePublicIP">If true and the public IP address is available from the STUN client then
        /// the public IP address will be used in the SDP otherwise the host machine's default IPv4 address will
        /// be used.</param>
        /// <returns>An SDP packet that can be used by a VoIP client when initiating a call.</returns>
        public SDP GetSDP(bool usePublicIP)
        {
            IPAddress rtpIPAddress = (usePublicIP && SoftphoneSTUNClient.PublicIPAddress != null) ? SoftphoneSTUNClient.PublicIPAddress : _defaultLocalAddress;

            var sdp = new SDP()
            {
                SessionId = Crypto.GetRandomInt(5).ToString(),
                Address = rtpIPAddress.ToString(),
                SessionName = "sipsorcery",
                Timing = "0 0",
                Connection = new SDPConnectionInformation(rtpIPAddress.ToString()),
            };

            if (_rtpAudioChannel != null)
            {
                var audioAnnouncement = new SDPMediaAnnouncement()
                {
                    Media = SDPMediaTypesEnum.audio,
                    MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat((int)SDPMediaFormatsEnum.PCMU, "PCMU", 8000) }
                };
                audioAnnouncement.Port = _rtpAudioChannel.RTPPort;
                sdp.Media.Add(audioAnnouncement);
            }

            if (_rtpVideoChannel != null)
            {
                var videoAnnouncement = new SDPMediaAnnouncement()
                {
                    Media = SDPMediaTypesEnum.video,
                    MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat(96, "VP8", 90000) }
                };
                videoAnnouncement.Port = _rtpVideoChannel.RTPPort;
                sdp.Media.Add(videoAnnouncement);
            }

            return sdp;
        }
示例#15
0
        public static SDP ParseSDPDescription(string sdpDescription)
        {
            try
            {
                if (sdpDescription != null && sdpDescription.Trim().Length > 0)
                {
                    SDP sdp = new SDP();
                    SDPMediaAnnouncement activeAnnouncement = null;

                    string[] sdpLines = Regex.Split(sdpDescription, CRLF);

                    foreach (string sdpLine in sdpLines)
                    {
                        if (sdpLine.Trim().StartsWith("v="))
                        {
                            if (!Decimal.TryParse(sdpLine.Substring(2), out sdp.Version))
                            {
                                logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + ".");
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("o="))
                        {
                            string[] ownerFields = sdpLine.Substring(2).Split(' ');
                            sdp.Username  = ownerFields[0];
                            sdp.SessionId = ownerFields[1];
                            Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion);
                            sdp.NetworkType = ownerFields[3];
                            sdp.AddressType = ownerFields[4];
                            sdp.Address     = ownerFields[5];
                        }
                        else if (sdpLine.Trim().StartsWith("s="))
                        {
                            sdp.SessionName = sdpLine.Substring(2);
                        }
                        else if (sdpLine.Trim().StartsWith("c="))
                        {
                            if (sdp.Connection == null)
                            {
                                sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine);
                            }

                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine);
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("b="))
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.BandwidthAttributes.Add(sdpLine.Substring(2));
                            }
                            else
                            {
                                sdp.BandwidthAttributes.Add(sdpLine.Substring(2));
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("t="))
                        {
                            sdp.Timing = sdpLine.Substring(2);
                        }
                        else if (sdpLine.Trim().StartsWith("m="))
                        {
                            Match mediaMatch = Regex.Match(sdpLine.Substring(2).Trim(), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$");
                            if (mediaMatch.Success)
                            {
                                SDPMediaAnnouncement announcement = new SDPMediaAnnouncement();
                                announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}"));
                                Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port);
                                announcement.Transport = mediaMatch.Result("${transport}");
                                announcement.ParseMediaFormats(mediaMatch.Result("${formats}"));
                                sdp.Media.Add(announcement);

                                activeAnnouncement = announcement;
                            }
                            else
                            {
                                logger.LogWarning("A media line in SDP was invalid: " + sdpLine.Substring(2) + ".");
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("a=" + ICE_UFRAG_ATTRIBUTE_PREFIX))
                        {
                            sdp.IceUfrag = sdpLine.Substring(sdpLine.IndexOf(':') + 1);
                        }
                        else if (sdpLine.Trim().StartsWith("a=" + ICE_PWD_ATTRIBUTE_PREFIX))
                        {
                            sdp.IcePwd = sdpLine.Substring(sdpLine.IndexOf(':') + 1);
                        }
                        else if (sdpLine.Trim().StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLine.Trim(), SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");
                                if (formatAttributeMatch.Success)
                                {
                                    int formatID;
                                    if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID))
                                    {
                                        activeAnnouncement.AddFormatAttribute(formatID, formatAttributeMatch.Result("${attribute}"));
                                    }
                                    else
                                    {
                                        logger.LogWarning("Invalid media format attribute in SDP: " + sdpLine);
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLine);
                                }
                            }
                            else
                            {
                                logger.LogWarning("There was no active media announcement for a media format attribute, ignoring.");
                            }
                        }
                        else if (sdpLine.Trim().StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLine.Trim(), SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");
                                if (formatAttributeMatch.Success)
                                {
                                    int formatID;
                                    if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID))
                                    {
                                        activeAnnouncement.AddFormatParameterAttribute(formatID, formatAttributeMatch.Result("${attribute}"));
                                    }
                                    else
                                    {
                                        logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine);
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLine);
                                }
                            }
                            else
                            {
                                logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring.");
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("a=" + ICE_CANDIDATE_ATTRIBUTE_PREFIX))
                        {
                            if (sdp.IceCandidates == null)
                            {
                                sdp.IceCandidates = new List <IceCandidate>();
                            }

                            sdp.IceCandidates.Add(IceCandidate.Parse(sdpLine.Substring(sdpLine.IndexOf(':') + 1)));
                        }
                        else
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.AddExtra(sdpLine);
                            }
                            else
                            {
                                sdp.AddExtra(sdpLine);
                            }
                        }
                    }

                    return(sdp);
                }
                else
                {
                    return(null);
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception ParseSDPDescription. " + excp.Message);
                throw excp;
            }
        }
示例#16
0
 public void SetRemoteSDP(SDP remoteSDP)
 {
     _rtpManager.SetRemoteSDP(remoteSDP);
 }
示例#17
0
        /// <summary>
        /// Initialises the WebRTC session by carrying out the ICE connectivity steps and when complete
        /// handing the RTP socket off for the DTLS handshake. Once the handshake is complete the session
        /// is ready for to exchange encrypted RTP and RTCP packets.
        /// </summary>
        /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN
        /// server in this session's ICE candidate gathering.</param>
        public async Task Initialise(DoDtlsHandshakeDelegate doDtlsHandshake, IPEndPoint turnServerEndPoint)
        {
            try
            {
                _doDtlsHandshake    = doDtlsHandshake;
                _turnServerEndPoint = turnServerEndPoint;

                DateTime startGatheringTime = DateTime.Now;

                IceConnectionState = IceConnectionStatesEnum.Gathering;

                await GetIceCandidatesAsync();

                logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}.");

                IceConnectionState = IceConnectionStatesEnum.GatheringComplete;

                if (LocalIceCandidates.Count == 0)
                {
                    logger.LogWarning("No local socket candidates were found for WebRTC call closing.");
                    Close("No local ICE candidates available.");
                }
                else
                {
                    bool includeAudioOffer          = _supportedAudioFormats?.Count() > 0;
                    bool includeVideoOffer          = _supportedVideoFormats?.Count() > 0;
                    bool haveIceCandidatesBeenAdded = false;
                    bool isMediaBundle = includeAudioOffer && includeVideoOffer;    // Is this SDP offer bundling audio and video on the same RTP connection.

                    string localIceCandidateString = null;

                    foreach (var iceCandidate in LocalIceCandidates)
                    {
                        localIceCandidateString += iceCandidate.ToString();
                    }

                    LocalIceUser     = LocalIceUser ?? Crypto.GetRandomString(20);
                    LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20);

                    SDP offerSdp = new SDP(IPAddress.Loopback);
                    offerSdp.SessionId = Crypto.GetRandomInt(5).ToString();

                    // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed
                    // on a single RTP socket.
                    if (isMediaBundle)
                    {
                        offerSdp.Group = MEDIA_GROUPING;
                    }

                    if (includeAudioOffer)
                    {
                        SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.audio,
                            _rtpChannel.RTPPort,
                            _supportedAudioFormats);

                        audioAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            audioAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        audioAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        audioAnnouncement.IceUfrag        = LocalIceUser;
                        audioAnnouncement.IcePwd          = LocalIcePassword;
                        audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        audioAnnouncement.AddExtra(SETUP_ATTRIBUTE);
                        audioAnnouncement.MediaStreamStatus = AudioStreamStatus;

                        if (isMediaBundle)
                        {
                            audioAnnouncement.MediaID = AUDIO_MEDIA_ID;
                        }

                        offerSdp.Media.Add(audioAnnouncement);
                    }

                    if (includeVideoOffer)
                    {
                        SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.video,
                            _rtpChannel.RTPPort,
                            _supportedVideoFormats);

                        videoAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            videoAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        videoAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        videoAnnouncement.IceUfrag        = LocalIceUser;
                        videoAnnouncement.IcePwd          = LocalIcePassword;
                        videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        videoAnnouncement.AddExtra(SETUP_ATTRIBUTE);
                        videoAnnouncement.MediaStreamStatus = VideoStreamStatus;

                        if (isMediaBundle)
                        {
                            videoAnnouncement.MediaID = VIDEO_MEDIA_ID;
                        }

                        offerSdp.Media.Add(videoAnnouncement);
                    }

                    SDP = offerSdp;

                    OnSdpOfferReady?.Invoke(SDP);
                }

                // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check.
                // If there are no remote candidates this call will end up being a NOP.
                SendStunConnectivityChecks(null);

                if (_doDtlsHandshake != null)
                {
                    _ = Task.Run(() =>
                    {
                        int result = _doDtlsHandshake(this);
                        IsDtlsNegotiationComplete = (result == 0);
                    });
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception WebRtcPeer.Initialise. " + excp);
                Close(excp.Message);
            }
        }
示例#18
0
        /// <summary>
        /// This set remote description overload is a convenience method for SIP/VoIP callers
        /// instead of WebRTC callers. The method signature better matches what the SIP
        /// user agent is expecting.
        /// TODO: Using two very similar overloads could cause confusion. Possibly
        /// consolidate.
        /// </summary>
        /// <param name="sdpType">Whether the remote SDP is an offer or answer.</param>
        /// <param name="sessionDescription">The SDP from the remote party.</param>
        /// <returns>The result of attempting to set the remote description.</returns>
        public override SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SDP sessionDescription)
        {
            RTCSessionDescriptionInit init = new RTCSessionDescriptionInit
            {
                sdp  = sessionDescription.ToString(),
                type = (sdpType == SdpType.answer) ? RTCSdpType.answer : RTCSdpType.offer
            };

            return(setRemoteDescription(init));
        }
示例#19
0
        private void Answered(SDP xmppSDP)
        {
            //Console.WriteLine("Yay call answered.");
            //Console.WriteLine(sdp.ToString());
            m_xmppServerEndPoint = SDP.GetSDPRTPEndPoint(xmppSDP.ToString());
            logger.Debug("Sending STUN binding request to " + m_xmppServerEndPoint + ".");
            STUNMessage initMessage = new STUNMessage(STUNMessageTypesEnum.BindingRequest);
            initMessage.AddUsernameAttribute(xmppSDP.IceUfrag + m_localSTUNUFrag);
            byte[] stunMessageBytes = initMessage.ToByteBuffer();
            m_xmppMediaSocket.Send(stunMessageBytes, stunMessageBytes.Length, m_xmppServerEndPoint);

            m_uas.Answer("application/sdp", GetSDPForSIPResponse().ToString(), null, SIPDialogueTransferModesEnum.NotAllowed);
        }
示例#20
0
        public static SDP ParseSDPDescription(string sdpDescription)
        {
            try
            {
                if (sdpDescription != null && sdpDescription.Trim().Length > 0)
                {
                    SDP sdp = new SDP();
                    sdp.m_rawSdp = sdpDescription;
                    int mLineIndex = 0;
                    SDPMediaAnnouncement activeAnnouncement = null;

                    string[] sdpLines = Regex.Split(sdpDescription, CRLF);

                    foreach (string sdpLine in sdpLines)
                    {
                        string sdpLineTrimmed = sdpLine.Trim();

                        switch (sdpLineTrimmed)
                        {
                        case var l when l.StartsWith("v="):
                            if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version))
                            {
                                logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + ".");
                            }
                            break;

                        case var l when l.StartsWith("o="):
                            string[] ownerFields = sdpLineTrimmed.Substring(2).Split(' ');

                            sdp.Username  = ownerFields[0];
                            sdp.SessionId = ownerFields[1];
                            Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion);
                            sdp.NetworkType   = ownerFields[3];
                            sdp.AddressType   = ownerFields[4];
                            sdp.AddressOrHost = ownerFields[5];
                            break;

                        case var l when l.StartsWith("s="):
                            sdp.SessionName = sdpLineTrimmed.Substring(2);

                            break;

                        case var l when l.StartsWith("c="):

                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed);
                            }
                            else if (sdp.Connection == null)
                            {
                                sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed);
                            }
                            else
                            {
                                logger.LogWarning("The SDP message had a duplicate connection attribute which was ignored.");
                            }

                            break;

                        case var l when l.StartsWith("b="):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2));
                            }
                            else
                            {
                                sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2));
                            }
                            break;

                        case var l when l.StartsWith("t="):
                            sdp.Timing = sdpLineTrimmed.Substring(2);

                            break;

                        case var l when l.StartsWith("m="):
                            Match mediaMatch = Regex.Match(sdpLineTrimmed.Substring(2), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$");

                            if (mediaMatch.Success)
                            {
                                SDPMediaAnnouncement announcement = new SDPMediaAnnouncement();
                                announcement.MLineIndex = mLineIndex;
                                announcement.Media      = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}"));
                                Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port);
                                announcement.Transport = mediaMatch.Result("${transport}");
                                announcement.ParseMediaFormats(mediaMatch.Result("${formats}"));
                                announcement.MediaStreamStatus = sdp.SessionMediaStreamStatus != null ? sdp.SessionMediaStreamStatus.Value :
                                                                 MediaStreamStatusEnum.SendRecv;
                                sdp.Media.Add(announcement);

                                activeAnnouncement = announcement;
                            }
                            else
                            {
                                logger.LogWarning("A media line in SDP was invalid: " + sdpLineTrimmed.Substring(2) + ".");
                            }

                            mLineIndex++;
                            break;

                        case var x when x.StartsWith($"a={GROUP_ATRIBUTE_PREFIX}"):
                            sdp.Group = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                            break;

                        case var x when x.StartsWith($"a={ICE_UFRAG_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            break;

                        case var x when x.StartsWith($"a={ICE_PWD_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            break;

                        case var x when x.StartsWith($"a={DTLS_FINGERPRINT_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            break;

                        case var x when x.StartsWith($"a={ICE_CANDIDATE_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                if (activeAnnouncement.IceCandidates == null)
                                {
                                    activeAnnouncement.IceCandidates = new List <string>();
                                }
                                activeAnnouncement.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1));
                            }
                            else
                            {
                                if (sdp.IceCandidates == null)
                                {
                                    sdp.IceCandidates = new List <string>();
                                }
                                sdp.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1));
                            }
                            break;

                        case var x when x == $"a={END_ICE_CANDIDATES_ATTRIBUTE}":
                            // TODO: Set a flag.
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");

                                if (formatAttributeMatch.Success)
                                {
                                    string formatID = formatAttributeMatch.Result("${id}");
                                    string rtpmap   = formatAttributeMatch.Result("${attribute}");

                                    if (activeAnnouncement.Media == SDPMediaTypesEnum.application)
                                    {
                                        var appMediaFormat = activeAnnouncement.ApplicationMediaFormats.Where(x => x.ID == formatID).FirstOrDefault();
                                        if (appMediaFormat.ID != null)
                                        {
                                            appMediaFormat.Rtpmap = rtpmap;
                                        }
                                    }
                                    else
                                    {
                                        if (Int32.TryParse(formatID, out int id))
                                        {
                                            if (activeAnnouncement.MediaFormats.ContainsKey(id))
                                            {
                                                activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedRtpmap(rtpmap, activeAnnouncement.MediaFormats[id]);
                                            }
                                            else
                                            {
                                                activeAnnouncement.MediaFormats.Add(id, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, id, rtpmap, null));
                                            }
                                        }
                                        else
                                        {
                                            logger.LogWarning("Invalid media format attribute in SDP: " + sdpLine);
                                        }
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLineTrimmed);
                                }
                            }
                            else
                            {
                                logger.LogWarning("There was no active media announcement for a media format attribute, ignoring.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");

                                if (formatAttributeMatch.Success)
                                {
                                    string formatID = formatAttributeMatch.Result("${id}");
                                    string fmtp     = formatAttributeMatch.Result("${attribute}");

                                    if (activeAnnouncement.Media == SDPMediaTypesEnum.application)
                                    {
                                        var appMediaFormat = activeAnnouncement.ApplicationMediaFormats.Where(x => x.ID == formatID).FirstOrDefault();
                                        if (appMediaFormat.ID != null)
                                        {
                                            appMediaFormat.Fmtmp = fmtp;
                                        }
                                    }
                                    else
                                    {
                                        if (Int32.TryParse(formatID, out int id))
                                        {
                                            if (activeAnnouncement.MediaFormats.ContainsKey(id))
                                            {
                                                activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedFmtp(fmtp, activeAnnouncement.MediaFormats[id]);
                                            }
                                            else
                                            {
                                                activeAnnouncement.MediaFormats.Add(id, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, id, null, fmtp));
                                            }
                                        }
                                        else
                                        {
                                            logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine);
                                        }
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLineTrimmed);
                                }
                            }
                            else
                            {
                                logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring.");
                            }
                            break;

                        case var l when l.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX):
                            //2018-12-21 rj2: add a=crypto
                            if (activeAnnouncement != null)
                            {
                                try
                                {
                                    activeAnnouncement.AddCryptoLine(sdpLineTrimmed);
                                }
                                catch (FormatException fex)
                                {
                                    logger.LogWarning("Error Parsing SDP-Line(a=crypto) " + fex);
                                }
                            }
                            break;

                        case var x when x.StartsWith($"a={MEDIA_ID_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.MediaID = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                logger.LogWarning("A media ID can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string[] fields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' ');

                                // Set the ID.
                                if (fields.Length > 0)
                                {
                                    activeAnnouncement.SsrcGroupID = fields[0];
                                }

                                // Add attributes for each of the SSRC values.
                                for (int i = 1; i < fields.Length; i++)
                                {
                                    if (uint.TryParse(fields[i], out var ssrc))
                                    {
                                        activeAnnouncement.SsrcAttributes.Add(new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID));
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("A ssrc-group ID can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string[] ssrcFields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' ');

                                if (ssrcFields.Length > 0 && uint.TryParse(ssrcFields[0], out var ssrc))
                                {
                                    var ssrcAttribute = activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc);
                                    if (ssrcAttribute == null)
                                    {
                                        ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null);
                                        activeAnnouncement.SsrcAttributes.Add(ssrcAttribute);
                                    }

                                    if (ssrcFields.Length > 1)
                                    {
                                        if (ssrcFields[1].StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX))
                                        {
                                            ssrcAttribute.Cname = ssrcFields[1].Substring(ssrcFields[1].IndexOf(':') + 1);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("An ssrc attribute can only be set on a media announcement.");
                            }
                            break;

                        case var x when MediaStreamStatusType.IsMediaStreamStatusAttribute(x, out var mediaStreamStatus):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.MediaStreamStatus = mediaStreamStatus;
                            }
                            else
                            {
                                sdp.SessionMediaStreamStatus = mediaStreamStatus;
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.SctpMap = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                                (var sctpPortStr, _, var maxMessageSizeStr) = activeAnnouncement.SctpMap.Split(' ');

                                if (ushort.TryParse(sctpPortStr, out var sctpPort))
                                {
                                    activeAnnouncement.SctpPort = sctpPort;
                                }
                                else
                                {
                                    logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port.");
                                }

                                if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize))
                                {
                                    logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.");
                                }
                            }
                            else
                            {
                                logger.LogWarning("An sctpmap attribute can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string sctpPortStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                                if (ushort.TryParse(sctpPortStr, out var sctpPort))
                                {
                                    activeAnnouncement.SctpPort = sctpPort;
                                }
                                else
                                {
                                    logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port.");
                                }
                            }
                            else
                            {
                                logger.LogWarning("An sctp-port attribute can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string maxMessageSizeStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                                if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize))
                                {
                                    logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.");
                                }
                            }
                            else
                            {
                                logger.LogWarning("A max-message-size attribute can only be set on a media announcement.");
                            }
                            break;

                        default:
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.AddExtra(sdpLineTrimmed);
                            }
                            else
                            {
                                sdp.AddExtra(sdpLineTrimmed);
                            }
                            break;
                        }
                    }

                    return(sdp);
                }
                else
                {
                    return(null);
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception ParseSDPDescription. " + excp.Message);
                throw;
            }
        }
示例#21
0
        public static SDP ParseSDPDescription(string sdpDescription)
        {
            try
            {
                if (sdpDescription != null && sdpDescription.Trim().Length > 0)
                {
                    SDP sdp = new SDP();
                    sdp.m_rawSdp = sdpDescription;
                    int mLineIndex = 0;
                    SDPMediaAnnouncement activeAnnouncement = null;

                    // If a media announcement fmtp atribute is found before the rtpmap it will be stored
                    // in this dictionary. A dynamic media format type cannot be created without an rtpmap.
                    Dictionary <int, string> _pendingFmtp = new Dictionary <int, string>();

                    //string[] sdpLines = Regex.Split(sdpDescription, CRLF);
                    string[] sdpLines = sdpDescription.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (string sdpLine in sdpLines)
                    {
                        string sdpLineTrimmed = sdpLine.Trim();

                        switch (sdpLineTrimmed)
                        {
                        case var l when l.StartsWith("v="):
                            if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version))
                            {
                                logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + ".");
                            }
                            break;

                        case var l when l.StartsWith("o="):
                            string[] ownerFields = sdpLineTrimmed.Substring(2).Split(' ');

                            sdp.Username  = ownerFields[0];
                            sdp.SessionId = ownerFields[1];
                            Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion);
                            sdp.NetworkType   = ownerFields[3];
                            sdp.AddressType   = ownerFields[4];
                            sdp.AddressOrHost = ownerFields[5];
                            break;

                        case var l when l.StartsWith("s="):
                            sdp.SessionName = sdpLineTrimmed.Substring(2);

                            break;

                        case var l when l.StartsWith("i="):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.MediaDescription = sdpLineTrimmed.Substring(2);
                            }
                            else
                            {
                                sdp.SessionDescription = sdpLineTrimmed.Substring(2);
                            }

                            break;

                        case var l when l.StartsWith("c="):

                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed);
                            }
                            else if (sdp.Connection == null)
                            {
                                sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed);
                            }
                            else
                            {
                                logger.LogWarning("The SDP message had a duplicate connection attribute which was ignored.");
                            }

                            break;

                        case var l when l.StartsWith("b="):
                            if (activeAnnouncement != null)
                            {
                                if (l.StartsWith(SDPMediaAnnouncement.TIAS_BANDWIDTH_ATTRIBUE_PREFIX))
                                {
                                    if (uint.TryParse(l.Substring(l.IndexOf(':') + 1), out uint tias))
                                    {
                                        activeAnnouncement.TIASBandwidth = tias;
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2));
                                }
                            }
                            else
                            {
                                sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2));
                            }
                            break;

                        case var l when l.StartsWith("t="):
                            sdp.Timing = sdpLineTrimmed.Substring(2);

                            break;

                        case var l when l.StartsWith("m="):
                            Match mediaMatch = Regex.Match(sdpLineTrimmed.Substring(2), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$");

                            if (mediaMatch.Success)
                            {
                                SDPMediaAnnouncement announcement = new SDPMediaAnnouncement();
                                announcement.MLineIndex = mLineIndex;
                                announcement.Media      = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}"));
                                Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port);
                                announcement.Transport = mediaMatch.Result("${transport}");
                                announcement.ParseMediaFormats(mediaMatch.Result("${formats}"));
                                if (announcement.Media == SDPMediaTypesEnum.audio || announcement.Media == SDPMediaTypesEnum.video)
                                {
                                    announcement.MediaStreamStatus = sdp.SessionMediaStreamStatus != null ? sdp.SessionMediaStreamStatus.Value :
                                                                     MediaStreamStatusEnum.SendRecv;
                                }
                                sdp.Media.Add(announcement);

                                activeAnnouncement = announcement;
                            }
                            else
                            {
                                logger.LogWarning("A media line in SDP was invalid: " + sdpLineTrimmed.Substring(2) + ".");
                            }

                            mLineIndex++;
                            break;

                        case var x when x.StartsWith($"a={GROUP_ATRIBUTE_PREFIX}"):
                            sdp.Group = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                            break;

                        case var x when x.StartsWith($"a={ICE_UFRAG_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            break;

                        case var x when x.StartsWith($"a={ICE_PWD_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            break;

                        case var x when x.StartsWith($"a={ICE_SETUP_ATTRIBUTE_PREFIX}"):
                            int colonIndex = sdpLineTrimmed.IndexOf(':');

                            if (colonIndex != -1 && sdpLineTrimmed.Length > colonIndex)
                            {
                                string iceRoleStr = sdpLineTrimmed.Substring(colonIndex + 1).Trim();
                                if (Enum.TryParse <IceRolesEnum>(iceRoleStr, true, out var iceRole))
                                {
                                    if (activeAnnouncement != null)
                                    {
                                        activeAnnouncement.IceRole = iceRole;
                                    }
                                    else
                                    {
                                        sdp.IceRole = iceRole;
                                    }
                                }
                                else
                                {
                                    logger.LogWarning($"ICE role was not recognised from SDP attribute: {sdpLineTrimmed}.");
                                }
                            }
                            else
                            {
                                logger.LogWarning($"ICE role SDP attribute was missing the mandatory colon: {sdpLineTrimmed}.");
                            }
                            break;

                        case var x when x.StartsWith($"a={DTLS_FINGERPRINT_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            break;

                        case var x when x.StartsWith($"a={ICE_CANDIDATE_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                if (activeAnnouncement.IceCandidates == null)
                                {
                                    activeAnnouncement.IceCandidates = new List <string>();
                                }
                                activeAnnouncement.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1));
                            }
                            else
                            {
                                if (sdp.IceCandidates == null)
                                {
                                    sdp.IceCandidates = new List <string>();
                                }
                                sdp.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1));
                            }
                            break;

                        case var x when x == $"a={END_ICE_CANDIDATES_ATTRIBUTE}":
                            // TODO: Set a flag.
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video)
                                {
                                    // Parse the rtpmap attribute for audio/video announcements.
                                    Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");
                                    if (formatAttributeMatch.Success)
                                    {
                                        string formatID = formatAttributeMatch.Result("${id}");
                                        string rtpmap   = formatAttributeMatch.Result("${attribute}");

                                        if (Int32.TryParse(formatID, out int id))
                                        {
                                            if (activeAnnouncement.MediaFormats.ContainsKey(id))
                                            {
                                                activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedRtpmap(rtpmap, activeAnnouncement.MediaFormats[id]);
                                            }
                                            else
                                            {
                                                string fmtp = _pendingFmtp.ContainsKey(id) ? _pendingFmtp[id] : null;
                                                activeAnnouncement.MediaFormats.Add(id, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, id, rtpmap, fmtp));
                                            }
                                        }
                                        else
                                        {
                                            logger.LogWarning("Non-numeric audio/video media format attribute in SDP: " + sdpLine);
                                        }
                                    }
                                    else
                                    {
                                        activeAnnouncement.AddExtra(sdpLineTrimmed);
                                    }
                                }
                                else
                                {
                                    // Parse the rtpmap attribute for NON audio/video announcements.
                                    Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\S+)\s+(?<attribute>.*)$");
                                    if (formatAttributeMatch.Success)
                                    {
                                        string formatID = formatAttributeMatch.Result("${id}");
                                        string rtpmap   = formatAttributeMatch.Result("${attribute}");

                                        if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID))
                                        {
                                            activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedRtpmap(rtpmap);
                                        }
                                        else
                                        {
                                            activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, rtpmap, null));
                                        }
                                    }
                                    else
                                    {
                                        activeAnnouncement.AddExtra(sdpLineTrimmed);
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("There was no active media announcement for a media format attribute, ignoring.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video)
                                {
                                    // Parse the fmtp attribute for audio/video announcements.
                                    Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");
                                    if (formatAttributeMatch.Success)
                                    {
                                        string avFormatID = formatAttributeMatch.Result("${id}");
                                        string fmtp       = formatAttributeMatch.Result("${attribute}");

                                        if (Int32.TryParse(avFormatID, out int id))
                                        {
                                            if (activeAnnouncement.MediaFormats.ContainsKey(id))
                                            {
                                                activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedFmtp(fmtp, activeAnnouncement.MediaFormats[id]);
                                            }
                                            else
                                            {
                                                // Store the fmtp attribute for use when the rtpmap attribute turns up.
                                                if (_pendingFmtp.ContainsKey(id))
                                                {
                                                    _pendingFmtp.Remove(id);
                                                }
                                                _pendingFmtp.Add(id, fmtp);
                                            }
                                        }
                                        else
                                        {
                                            logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine);
                                        }
                                    }
                                    else
                                    {
                                        activeAnnouncement.AddExtra(sdpLineTrimmed);
                                    }
                                }
                                else
                                {
                                    // Parse the fmtp attribute for NON audio/video announcements.
                                    Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\S+)\s+(?<attribute>.*)$");
                                    if (formatAttributeMatch.Success)
                                    {
                                        string formatID = formatAttributeMatch.Result("${id}");
                                        string fmtp     = formatAttributeMatch.Result("${attribute}");

                                        if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID))
                                        {
                                            activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedFmtp(fmtp);
                                        }
                                        else
                                        {
                                            activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, null, fmtp));
                                        }
                                    }
                                    else
                                    {
                                        activeAnnouncement.AddExtra(sdpLineTrimmed);
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring.");
                            }
                            break;

                        case var l when l.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX):
                            //2018-12-21 rj2: add a=crypto
                            if (activeAnnouncement != null)
                            {
                                try
                                {
                                    activeAnnouncement.AddCryptoLine(sdpLineTrimmed);
                                }
                                catch (FormatException fex)
                                {
                                    logger.LogWarning("Error Parsing SDP-Line(a=crypto) " + fex);
                                }
                            }
                            break;

                        case var x when x.StartsWith($"a={MEDIA_ID_ATTRIBUTE_PREFIX}"):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.MediaID = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                logger.LogWarning("A media ID can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string[] fields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' ');

                                // Set the ID.
                                if (fields.Length > 0)
                                {
                                    activeAnnouncement.SsrcGroupID = fields[0];
                                }

                                // Add attributes for each of the SSRC values.
                                for (int i = 1; i < fields.Length; i++)
                                {
                                    if (uint.TryParse(fields[i], out var ssrc))
                                    {
                                        activeAnnouncement.SsrcAttributes.Add(new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID));
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("A ssrc-group ID can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string[] ssrcFields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' ');

                                if (ssrcFields.Length > 0 && uint.TryParse(ssrcFields[0], out var ssrc))
                                {
                                    var ssrcAttribute = activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc);
                                    if (ssrcAttribute == null)
                                    {
                                        ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null);
                                        activeAnnouncement.SsrcAttributes.Add(ssrcAttribute);
                                    }

                                    if (ssrcFields.Length > 1)
                                    {
                                        if (ssrcFields[1].StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX))
                                        {
                                            ssrcAttribute.Cname = ssrcFields[1].Substring(ssrcFields[1].IndexOf(':') + 1);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("An ssrc attribute can only be set on a media announcement.");
                            }
                            break;

                        case var x when MediaStreamStatusType.IsMediaStreamStatusAttribute(x, out var mediaStreamStatus):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.MediaStreamStatus = mediaStreamStatus;
                            }
                            else
                            {
                                sdp.SessionMediaStreamStatus = mediaStreamStatus;
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.SctpMap = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                                (var sctpPortStr, _, var maxMessageSizeStr) = activeAnnouncement.SctpMap.Split(' ');

                                if (ushort.TryParse(sctpPortStr, out var sctpPort))
                                {
                                    activeAnnouncement.SctpPort = sctpPort;
                                }
                                else
                                {
                                    logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port.");
                                }

                                if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize))
                                {
                                    logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.");
                                }
                            }
                            else
                            {
                                logger.LogWarning("An sctpmap attribute can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string sctpPortStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                                if (ushort.TryParse(sctpPortStr, out var sctpPort))
                                {
                                    activeAnnouncement.SctpPort = sctpPort;
                                }
                                else
                                {
                                    logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port.");
                                }
                            }
                            else
                            {
                                logger.LogWarning("An sctp-port attribute can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string maxMessageSizeStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);

                                if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize))
                                {
                                    logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.");
                                }
                            }
                            else
                            {
                                logger.LogWarning("A max-message-size attribute can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_ACCEPT_TYPES_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string acceptTypesStr  = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                                var    acceptTypesList = acceptTypesStr.Trim().Split(' ').ToList();

                                activeAnnouncement.MessageMediaFormat.AcceptTypes = acceptTypesList;
                            }
                            else
                            {
                                logger.LogWarning("A accept-types attribute can only be set on a media announcement.");
                            }
                            break;

                        case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_MSRP_PREFIX):
                            if (activeAnnouncement != null)
                            {
                                string pathStr        = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                                string pathTrimmedStr = pathStr.Substring(pathStr.IndexOf(':') + 3);

                                activeAnnouncement.MessageMediaFormat.IP = pathTrimmedStr.Substring(0, pathTrimmedStr.IndexOf(':'));

                                pathTrimmedStr = pathTrimmedStr.Substring(pathTrimmedStr.IndexOf(':') + 1);
                                activeAnnouncement.MessageMediaFormat.Port = pathTrimmedStr.Substring(0, pathTrimmedStr.IndexOf('/'));

                                pathTrimmedStr = pathTrimmedStr.Substring(pathTrimmedStr.IndexOf('/') + 1);
                                activeAnnouncement.MessageMediaFormat.Endpoint = pathTrimmedStr;
                            }
                            else
                            {
                                logger.LogWarning("A path attribute can only be set on a media announcement.");
                            }
                            break;

                        default:
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.AddExtra(sdpLineTrimmed);
                            }
                            else
                            {
                                sdp.AddExtra(sdpLineTrimmed);
                            }
                            break;
                        }
                    }

                    return(sdp);
                }
                else
                {
                    return(null);
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception ParseSDPDescription. " + excp.Message);
                throw;
            }
        }
示例#22
0
        private SDP GetSDPForXMPPRequest()
        {
            m_localSTUNUFrag = Crypto.GetRandomString(8);

            SDP sdp = new SDP()
            {
                IcePwd = Crypto.GetRandomString(12),
                IceUfrag = m_localSTUNUFrag,
                Connection = new SDPConnectionInformation(m_localXMPPEndPoint.Address.ToString()),
                Media = new List<SDPMediaAnnouncement>()
                {
                    new SDPMediaAnnouncement(m_localXMPPEndPoint.Port)
                    {
                        MediaFormats = new List<SDPMediaFormat>()
                        {
                            new SDPMediaFormat(0, "PCMU", 8000)
                        }
                    }
                },
            };

            return sdp;
        }
示例#23
0
        /// <summary>
        /// Event handler for an answer on an outgoing Google Voice call.
        /// </summary>
        /// <param name="xmppSDP">The SDP packet received from the Google Voice gateway.</param>
        private void XMPPAnswered(SDP xmppSDP)
        {
            StatusMessage("Google Voice call answered.");

            IPEndPoint remoteSDPEndPoint = SDP.GetSDPRTPEndPoint(xmppSDP.ToString());
            _rtpManager.SetRemoteRTPEndPoints(remoteSDPEndPoint, null);

            // Google Voice require that a STUN exchange occurs on the RTP socket before the RTP packet can flow.
            // This code block sends a STUN binding request to the Google Voice gateway.
            STUNMessage initMessage = new STUNMessage(STUNMessageTypesEnum.BindingRequest);
            initMessage.AddUsernameAttribute(xmppSDP.IceUfrag + m_localSTUNUFrag);
            byte[] stunMessageBytes = initMessage.ToByteBuffer();
            _rtpManager.SendRTPRaw(stunMessageBytes, stunMessageBytes.Length);
        }
示例#24
0
        public static SDP GetSDP(string ipAddress, int port, string username, string password, List<XElement> payloads)
        {
            string iceUsername = (username.IsNullOrBlank()) ? Crypto.GetRandomString(6) : username;
            string icePwd = (password.IsNullOrBlank()) ? Crypto.GetRandomString(6) : password;

            SDP sdp = new SDP()
            {
                Address = ipAddress,
                Username = "******",
                SessionId = Crypto.GetRandomString(5),
                AnnouncementVersion = Crypto.GetRandomInt(5),
                Connection = new SDPConnectionInformation(ipAddress),
                Timing = "0 0",
                IceUfrag = iceUsername,
                IcePwd = icePwd,
                Media = new List<SDPMediaAnnouncement>()
                {
                    new SDPMediaAnnouncement(port)
                    {
                        //BandwidthAttributes = new List<string>(){"RS:0", "RR:0"} // Indicate that RTCP is not being used.
                    }
                }
            };

            sdp.ExtraAttributes.Add("a=candidate:1 1 UDP " + Crypto.GetRandomString(10) + " " + ipAddress + " " + port + " typ host");
            sdp.ExtraAttributes.Add("a=candidate:1 2 UDP " + Crypto.GetRandomString(10) + " " + ipAddress + " " + (port + 1) + " typ host");

            foreach (XElement payload in payloads)
            {
                int formatID;
                Int32.TryParse(payload.Attribute("id").Value, out formatID);
                string name = payload.Attribute("name").Value;
                int clockRate = 0;
                if (payload.Attribute("clockrate") != null)
                {
                    Int32.TryParse(payload.Attribute("clockrate").Value, out clockRate);
                }

                if(clockRate == 0)
                {
                    sdp.Media[0].MediaFormats.Add(new SDPMediaFormat(formatID, name));
                }
                else
                {
                    sdp.Media[0].MediaFormats.Add(new SDPMediaFormat(formatID, name, clockRate));
                }
            }

            //Console.WriteLine("SDPToJingle SDP=> " + sdp.ToString());

            return sdp;
        }
示例#25
0
        public static SDP ParseSDPDescription(string sdpDescription)
        {
            try
            {
                if (sdpDescription != null && sdpDescription.Trim().Length > 0)
                {
                    SDP sdp = new SDP();
                    SDPMediaAnnouncement media = new SDPMediaAnnouncement();

                    string[] sdpLines = Regex.Split(sdpDescription, CRLF);

                    //added WebRTC handling -> dont parse SDP especially
                    sdp.isWebRTC = sdpDescription.Contains("a=source:webrtc");

                    if (sdp.isWebRTC)
                    {
                        foreach (string sdpLine in sdpLines)
                        {
                            sdp.ExtraAttributes.Add(sdpLine);
                        }
                    }
                    else
                    {
                        foreach (string sdpLine in sdpLines)
                        {
                            if (sdpLine.Trim().StartsWith("v="))
                            {
                                if (!Decimal.TryParse(sdpLine.Substring(2), out sdp.Version))
                                {
                                    logger.Warn("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + ".");
                                }
                            }
                            else if (sdpLine.Trim().StartsWith("o="))
                            {
                                string[] ownerFields = sdpLine.Substring(2).Split(' ');
                                sdp.Username = ownerFields[0];
                                sdp.SessionId = ownerFields[1];
                                Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion);
                                sdp.NetworkType = ownerFields[3];
                                sdp.AddressType = ownerFields[4];
                                sdp.Address = ownerFields[5];
                            }
                            else if (sdpLine.Trim().StartsWith("s="))
                            {
                                sdp.SessionName = sdpLine.Substring(2);
                            }
                            else if (sdpLine.Trim().StartsWith("c="))
                            {
                                sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine);
                            }
                            else if (sdpLine.Trim().StartsWith("t="))
                            {
                                sdp.Timing = sdpLine.Substring(2);
                            }
                            else if (sdpLine.Trim().StartsWith("m="))
                            {
                                Match mediaMatch = Regex.Match(sdpLine.Substring(2).Trim(), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)\s+(?<formats>.*)$");
                                if (mediaMatch.Success)
                                {
                                    media.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}"));
                                    Int32.TryParse(mediaMatch.Result("${port}"), out media.Port);
                                    media.Transport = mediaMatch.Result("${transport}");
                                    media.ParseMediaFormats(mediaMatch.Result("${formats}"));
                                }
                                else
                                {
                                    logger.Warn("A media line in SDP was invalid: " + sdpLine.Substring(2) + ".");
                                }
                            }
                            else if (sdpLine.Trim().StartsWith("a=" + ICE_UFRAG_ATTRIBUTE_PREFIX))
                            {
                                sdp.IceUfrag = sdpLine.Substring(sdpLine.IndexOf(':') + 1);
                            }
                            else if (sdpLine.Trim().StartsWith("a=" + ICE_PWD_ATTRIBUTE_PREFIX))
                            {
                                sdp.IcePwd = sdpLine.Substring(sdpLine.IndexOf(':') + 1);
                            }
                            else if (sdpLine.Trim().StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX))
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLine.Trim(), SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");
                                if (formatAttributeMatch.Success)
                                {
                                    int formatID;
                                    if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID))
                                    {
                                        media.AddFormatAttribute(formatID, formatAttributeMatch.Result("${attribute}"));
                                    }
                                    else
                                    {
                                        logger.Warn("Invalid media format attribute in SDP: " + sdpLine);
                                    }
                                }
                                else
                                {
                                    sdp.ExtraAttributes.Add(sdpLine);
                                }
                            }
                            else
                            {
                                sdp.ExtraAttributes.Add(sdpLine);
                            }
                        }
                        sdp.Media.Add(media);
                    }
                    return sdp;
                }
                else
                {
                    return null;
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception ParseSDPDescription. " + excp.Message);
                throw excp;
            }
        }
示例#26
0
        /// <summary>
        /// Gets an SDP packet that can be used by VoIP clients to negotiate an audio connection. The SDP will only
        /// offer PCMU since that's all I've gotten around to handling.
        /// </summary>
        /// <param name="usePublicIP">If true and the public IP address is available from the STUN client then
        /// the public IP address will be used in the SDP otherwise the hsot machine's default IPv4 address will
        /// be used.</param>
        /// <returns>An SDP packet that can be used by a VoIP client when initiating a call.</returns>
        public SDP GetSDP(bool usePublicIP)
        {
            IPAddress rtpIPAddress = (usePublicIP) ? SoftphoneSTUNClient.PublicIPAddress : _rtpEndPoint.Address;
            int rtpPort = _rtpEndPoint.Port;

            var sdp = new SDP()
            {
                SessionId = Crypto.GetRandomInt(6).ToString(),
                Address = rtpIPAddress.ToString(),
                SessionName = "sipsorcery",
                Timing = "0 0",
                Connection = new SDPConnectionInformation(rtpIPAddress.ToString()),
                Media = new List<SDPMediaAnnouncement>()
                {
                    new SDPMediaAnnouncement()
                    {
                        Media = SDPMediaTypesEnum.audio,
                        Port = rtpPort,
                        MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat((int)SDPMediaFormatsEnum.PCMU) }
                    }
                }
            };

            return sdp;
        }
示例#27
0
        /// <summary>
        /// Generates the base SDP for an offer or answer. The SDP will then be tailored depending
        /// on whether it's being used in an offer or an answer.
        /// </summary>
        private async Task <SDP> createBaseSdp()
        {
            DateTime startGatheringTime = DateTime.Now;

            IceConnectionState = IceConnectionStatesEnum.Gathering;

            await GetIceCandidatesAsync().ConfigureAwait(false);

            logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}.");

            IceConnectionState = IceConnectionStatesEnum.GatheringComplete;

            if (LocalIceCandidates.Count == 0)
            {
                //logger.LogWarning("No local socket candidates were found for WebRTC call closing.");
                //Close("No local ICE candidates available.");
                throw new ApplicationException("No local ICE candidates available.");
            }
            else
            {
                SDP offerSdp = new SDP(IPAddress.Loopback);
                offerSdp.SessionId = LocalSdpSessionID;

                bool   haveIceCandidatesBeenAdded = false;
                string localIceCandidateString    = null;

                foreach (var iceCandidate in LocalIceCandidates)
                {
                    localIceCandidateString += iceCandidate.ToString();
                }

                // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed
                // on a single RTP socket.
                if (AudioLocalTrack != null && VideoLocalTrack != null)
                {
                    offerSdp.Group = MEDIA_GROUPING;
                }

                // The media is being multiplexed so the audio and video RTP channel is the same.
                var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio);

                // --- Audio announcement ---
                if (AudioLocalTrack != null)
                {
                    SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement(
                        SDPMediaTypesEnum.audio,
                        rtpChannel.RTPPort,
                        AudioLocalTrack.Capabilties);

                    audioAnnouncement.Transport = RTP_MEDIA_PROFILE;

                    if (!haveIceCandidatesBeenAdded)
                    {
                        audioAnnouncement.IceCandidates = LocalIceCandidates;
                        haveIceCandidatesBeenAdded      = true;
                    }

                    audioAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                    audioAnnouncement.IceUfrag        = LocalIceUser;
                    audioAnnouncement.IcePwd          = LocalIcePassword;
                    audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                    audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                    audioAnnouncement.MediaStreamStatus = AudioLocalTrack.Transceiver.Direction;
                    audioAnnouncement.MediaID           = AudioLocalTrack.Transceiver.MID;

                    offerSdp.Media.Add(audioAnnouncement);
                }

                // --- Video announcement ---
                if (VideoLocalTrack != null)
                {
                    SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement(
                        SDPMediaTypesEnum.video,
                        rtpChannel.RTPPort,
                        VideoLocalTrack.Capabilties);

                    videoAnnouncement.Transport = RTP_MEDIA_PROFILE;

                    if (!haveIceCandidatesBeenAdded)
                    {
                        videoAnnouncement.IceCandidates = LocalIceCandidates;
                        haveIceCandidatesBeenAdded      = true;
                    }

                    videoAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                    videoAnnouncement.IceUfrag        = LocalIceUser;
                    videoAnnouncement.IcePwd          = LocalIcePassword;
                    videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                    videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                    videoAnnouncement.MediaStreamStatus = VideoLocalTrack.Transceiver.Direction;
                    videoAnnouncement.MediaID           = VideoLocalTrack.Transceiver.MID;

                    offerSdp.Media.Add(videoAnnouncement);
                }

                return(offerSdp);
            }
        }
示例#28
0
        private SDP GetSDPForSIPResponse()
        {
            SDP sdp = new SDP()
            {
                Address = m_localSIPEndPoint.Address.ToString(),
                Username = "******",
                SessionId = Crypto.GetRandomString(5),
                AnnouncementVersion = Crypto.GetRandomInt(5),
                Timing = "0 0",
                Connection = new SDPConnectionInformation(m_localSIPEndPoint.Address.ToString()),
                Media = new List<SDPMediaAnnouncement>()
                {
                    new SDPMediaAnnouncement(m_localSIPEndPoint.Port)
                    {
                        MediaFormats = new List<SDPMediaFormat>()
                        {
                            new SDPMediaFormat(0, "PCMU", 8000)
                        }
                    }
                },
            };

            return sdp;
        }
示例#29
0
        /// <summary>
        /// Updates the session after receiving the remote SDP.
        /// At this point check that the codecs match. We currently only support:
        ///  - Audio: PCMU,
        ///  - Video: VP8.
        /// If they are not available there's no point carrying on.
        /// </summary>
        /// <param name="sessionDescription">The answer/offer SDP from the remote party.</param>
        public SetDescriptionResultEnum setRemoteDescription(RTCSessionDescriptionInit init)
        {
            RTCSessionDescription description = new RTCSessionDescription {
                type = init.type, sdp = SDP.ParseSDPDescription(init.sdp)
            };

            remoteDescription = description;

            SDP remoteSdp = SDP.ParseSDPDescription(init.sdp);

            SdpType sdpType   = (init.type == RTCSdpType.offer) ? SdpType.offer : SdpType.answer;
            var     setResult = base.SetRemoteDescription(sdpType, remoteSdp);

            if (setResult == SetDescriptionResultEnum.OK)
            {
                string remoteIceUser     = remoteSdp.IceUfrag;
                string remoteIcePassword = remoteSdp.IcePwd;
                string dtlsFingerprint   = remoteSdp.DtlsFingerprint;

                var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault();
                if (audioAnnounce != null)
                {
                    remoteIceUser     = remoteIceUser ?? audioAnnounce.IceUfrag;
                    remoteIcePassword = remoteIcePassword ?? audioAnnounce.IcePwd;
                    dtlsFingerprint   = dtlsFingerprint ?? audioAnnounce.DtlsFingerprint;
                }

                var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault();
                if (videoAnnounce != null)
                {
                    if (remoteIceUser == null || remoteIcePassword == null || dtlsFingerprint == null)
                    {
                        remoteIceUser     = remoteIceUser ?? videoAnnounce.IceUfrag;
                        remoteIcePassword = remoteIcePassword ?? videoAnnounce.IcePwd;
                        dtlsFingerprint   = dtlsFingerprint ?? videoAnnounce.DtlsFingerprint;
                    }
                }

                SdpSessionID = remoteSdp.SessionId;

                if (init.type == RTCSdpType.answer)
                {
                    _rtpIceChannel.IsController = true;
                    // Set DTLS role to be server.
                    IceRole = IceRolesEnum.passive;
                }
                else
                {
                    // Set DTLS role to be server.
                    // As of 20 Jun 2020 the DTLS handshake logic is based on OpenSSL and the mechanism
                    // used hands over the socket handle to the C++ class. This logic works a lot better
                    // when acting as the server in the handshake.
                    IceRole = IceRolesEnum.passive;
                }

                if (remoteIceUser != null && remoteIcePassword != null)
                {
                    _rtpIceChannel.SetRemoteCredentials(remoteIceUser, remoteIcePassword);
                }

                if (!string.IsNullOrWhiteSpace(dtlsFingerprint))
                {
                    dtlsFingerprint = dtlsFingerprint.Trim().ToLower();

                    if (dtlsFingerprint.Length < DTLS_FINGERPRINT_DIGEST.Length + 1)
                    {
                        logger.LogWarning($"The DTLS fingerprint was too short.");
                        return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported);
                    }
                    else if (!dtlsFingerprint.StartsWith(DTLS_FINGERPRINT_DIGEST))
                    {
                        logger.LogWarning($"The DTLS fingerprint was supplied with an unsupported digest function (supported one is {DTLS_FINGERPRINT_DIGEST}): {dtlsFingerprint}.");
                        return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported);
                    }
                    else
                    {
                        dtlsFingerprint           = dtlsFingerprint.Substring(DTLS_FINGERPRINT_DIGEST.Length + 1).Trim().Replace(":", "").Replace(" ", "");
                        RemotePeerDtlsFingerprint = ByteBufferInfo.ParseHexStr(dtlsFingerprint);
                        logger.LogDebug($"The DTLS fingerprint for the remote peer's SDP {ByteBufferInfo.HexStr(RemotePeerDtlsFingerprint)}.");
                    }
                }
                else
                {
                    logger.LogWarning("The DTLS fingerprint was missing from the remote party's session description.");
                    return(SetDescriptionResultEnum.DtlsFingerprintMissing);
                }

                // All browsers seem to have gone to trickling ICE candidates now but just
                // in case one or more are given we can start the STUN dance immediately.
                if (remoteSdp.IceCandidates != null)
                {
                    foreach (var iceCandidate in remoteSdp.IceCandidates)
                    {
                        addIceCandidate(new RTCIceCandidateInit {
                            candidate = iceCandidate
                        });
                    }
                }

                foreach (var media in remoteSdp.Media)
                {
                    if (media.IceCandidates != null)
                    {
                        foreach (var iceCandidate in media.IceCandidates)
                        {
                            addIceCandidate(new RTCIceCandidateInit {
                                candidate = iceCandidate
                            });
                        }
                    }
                }

                signalingState = RTCSignalingState.have_remote_offer;
                onsignalingstatechange?.Invoke();
            }

            return(setResult);
        }
示例#30
0
        public static SDP ParseSDPDescription(string sdpDescription)
        {
            try
            {
                if (sdpDescription != null && sdpDescription.Trim().Length > 0)
                {
                    SDP sdp = new SDP();
                    SDPMediaAnnouncement activeAnnouncement = null;

                    string[] sdpLines = Regex.Split(sdpDescription, CRLF);

                    foreach (string sdpLine in sdpLines)
                    {
                        if (sdpLine.Trim().StartsWith("v="))
                        {
                            if (!Decimal.TryParse(sdpLine.Substring(2), out sdp.Version))
                            {
                                logger.Warn("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + ".");
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("o="))
                        {
                            string[] ownerFields = sdpLine.Substring(2).Split(' ');
                            sdp.Username = ownerFields[0];
                            sdp.SessionId = ownerFields[1];
                            Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion);
                            sdp.NetworkType = ownerFields[3];
                            sdp.AddressType = ownerFields[4];
                            sdp.Address = ownerFields[5];
                        }
                        else if (sdpLine.Trim().StartsWith("s="))
                        {
                            sdp.SessionName = sdpLine.Substring(2);
                        }
                        else if (sdpLine.Trim().StartsWith("c="))
                        {
                            if(activeAnnouncement != null)
                                activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine);
                            else
                                sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine);
                        }
                        else if(sdpLine.Trim().StartsWith("b="))
                        {
                            if(activeAnnouncement != null)
                                activeAnnouncement.BandwidthAttributes.Add(sdpLine.Substring(2));
                            else
                                sdp.BandwidthAttributes.Add(sdpLine.Substring(2));
                        }
                        else if (sdpLine.Trim().StartsWith("t="))
                        {
                            sdp.Timing = sdpLine.Substring(2);
                        }
                        else if (sdpLine.Trim().StartsWith("m="))
                        {
                            Match mediaMatch = Regex.Match(sdpLine.Substring(2).Trim(), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)\s+(?<formats>.*)$");
                            if (mediaMatch.Success)
                            {
                                SDPMediaAnnouncement announcement = new SDPMediaAnnouncement();
                                announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}"));
                                Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port);
                                announcement.Transport = mediaMatch.Result("${transport}");
                                announcement.ParseMediaFormats(mediaMatch.Result("${formats}"));
                                sdp.Media.Add(announcement);

                                activeAnnouncement = announcement;
                            }
                            else
                            {
                                logger.Warn("A media line in SDP was invalid: " + sdpLine.Substring(2) + ".");
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("a=" + ICE_UFRAG_ATTRIBUTE_PREFIX))
                        {
                            sdp.IceUfrag = sdpLine.Substring(sdpLine.IndexOf(':') + 1);
                        }
                        else if (sdpLine.Trim().StartsWith("a=" + ICE_PWD_ATTRIBUTE_PREFIX))
                        {
                            sdp.IcePwd = sdpLine.Substring(sdpLine.IndexOf(':') + 1);
                        }
                        else if (sdpLine.Trim().StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLine.Trim(), SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");
                                if (formatAttributeMatch.Success)
                                {
                                    int formatID;
                                    if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID))
                                    {
                                        activeAnnouncement.AddFormatAttribute(formatID, formatAttributeMatch.Result("${attribute}"));
                                    }
                                    else
                                    {
                                        logger.Warn("Invalid media format attribute in SDP: " + sdpLine);
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLine);
                                }
                            }
                            else
                            {
                                logger.Warn("There was no active media announcement for a media format attribute, ignoring.");
                            }
                        }
                        else if(sdpLine.Trim().StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX))
                        {
                            if(activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLine.Trim(), SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$");
                                if(formatAttributeMatch.Success)
                                {
                                    int formatID;
                                    if(Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID))
                                    {
                                        activeAnnouncement.AddFormatParameterAttribute(formatID, formatAttributeMatch.Result("${attribute}"));
                                    }
                                    else
                                    {
                                        logger.Warn("Invalid media format parameter attribute in SDP: " + sdpLine);
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLine);
                                }
                            }
                            else
                            {
                                logger.Warn("There was no active media announcement for a media format parameter attribute, ignoring.");
                            }
                        }
                        else if (sdpLine.Trim().StartsWith("a=" + ICE_CANDIDATE_ATTRIBUTE_PREFIX))
                        {
                            if(sdp.IceCandidates == null)
                            {
                                sdp.IceCandidates = new List<IceCandidate>();
                            }

                            sdp.IceCandidates.Add(IceCandidate.Parse(sdpLine.Substring(sdpLine.IndexOf(':') + 1)));
                        }
                        else
                        {
                            if(activeAnnouncement != null)
                                activeAnnouncement.AddExtra(sdpLine);
                            else
                                sdp.AddExtra(sdpLine);
                        }
                    }

                    return sdp;
                }
                else
                {
                    return null;
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception ParseSDPDescription. " + excp.Message);
                throw excp;
            }
        }
示例#31
0
 /// <summary>
 /// Sets the local SDP.
 /// </summary>
 /// <param name="sdp">The SDP to set.</param>
 public void setLocalDescription(SDP sdp)
 {
     SDP = sdp;
 }
示例#32
0
        public static SDP ParseSDPDescription(string sdpDescription)
        {
            try
            {
                if (sdpDescription != null && sdpDescription.Trim().Length > 0)
                {
                    SDP sdp = new SDP();
                    sdp.m_rawSdp = sdpDescription;
                    SDPMediaAnnouncement activeAnnouncement = null;

                    string[] sdpLines = Regex.Split(sdpDescription, CRLF);

                    foreach (string sdpLine in sdpLines)
                    {
                        string sdpLineTrimmed = sdpLine.Trim();
                        if (sdpLineTrimmed.StartsWith("v="))
                        {
                            if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version))
                            {
                                logger.LogWarning(
                                    "The Version value in an SDP description could not be parsed as a decimal: " +
                                    sdpLine + ".");
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("o="))
                        {
                            string[] ownerFields = sdpLineTrimmed.Substring(2).Split(' ');
                            sdp.Username  = ownerFields[0];
                            sdp.SessionId = ownerFields[1];
                            Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion);
                            sdp.NetworkType   = ownerFields[3];
                            sdp.AddressType   = ownerFields[4];
                            sdp.AddressOrHost = ownerFields[5];
                        }
                        else if (sdpLineTrimmed.StartsWith("s="))
                        {
                            sdp.SessionName = sdpLineTrimmed.Substring(2);
                        }
                        else if (sdpLineTrimmed.StartsWith("c="))
                        {
                            if (sdp.Connection == null)
                            {
                                sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed);
                            }

                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.Connection =
                                    SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed);
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("b="))
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2));
                            }
                            else
                            {
                                sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2));
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("t="))
                        {
                            sdp.Timing = sdpLineTrimmed.Substring(2);
                        }
                        else if (sdpLineTrimmed.StartsWith("m="))
                        {
                            Match mediaMatch = Regex.Match(sdpLineTrimmed.Substring(2),
                                                           @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$");
                            if (mediaMatch.Success)
                            {
                                SDPMediaAnnouncement announcement = new SDPMediaAnnouncement();
                                announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}"));
                                Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port);
                                announcement.Transport = mediaMatch.Result("${transport}");
                                announcement.ParseMediaFormats(mediaMatch.Result("${formats}"));
                                sdp.Media.Add(announcement);

                                activeAnnouncement = announcement;
                            }
                            else
                            {
                                logger.LogWarning("A media line in SDP was invalid: " + sdpLineTrimmed.Substring(2) +
                                                  ".");
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("a=" + GROUP_ATRIBUTE_PREFIX))
                        {
                            sdp.Group = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                        }
                        else if (sdpLineTrimmed.StartsWith("a=" + ICE_UFRAG_ATTRIBUTE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("a=" + ICE_PWD_ATTRIBUTE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("a=" + DTLS_FINGERPRINT_ATTRIBUTE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.DtlsFingerprint =
                                    sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                sdp.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith($"a={END_ICE_CANDIDATES_ATTRIBUTE}"))
                        {
                            // Do nothing.
                        }
                        else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLineTrimmed,
                                                                         SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX +
                                                                         @"(?<id>\d+)\s+(?<attribute>.*)$");
                                if (formatAttributeMatch.Success)
                                {
                                    int formatID;
                                    if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID))
                                    {
                                        activeAnnouncement.AddFormatAttribute(formatID,
                                                                              formatAttributeMatch.Result("${attribute}"));
                                    }
                                    else
                                    {
                                        logger.LogWarning("Invalid media format attribute in SDP: " + sdpLine);
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLineTrimmed);
                                }
                            }
                            else
                            {
                                logger.LogWarning(
                                    "There was no active media announcement for a media format attribute, ignoring.");
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX)
                                 )
                        {
                            if (activeAnnouncement != null)
                            {
                                Match formatAttributeMatch = Regex.Match(sdpLineTrimmed,
                                                                         SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX +
                                                                         @"(?<id>\d+)\s+(?<attribute>.*)$");
                                if (formatAttributeMatch.Success)
                                {
                                    int formatID;
                                    if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID))
                                    {
                                        activeAnnouncement.AddFormatParameterAttribute(formatID,
                                                                                       formatAttributeMatch.Result("${attribute}"));
                                    }
                                    else
                                    {
                                        logger.LogWarning("Invalid media format parameter attribute in SDP: " +
                                                          sdpLine);
                                    }
                                }
                                else
                                {
                                    activeAnnouncement.AddExtra(sdpLineTrimmed);
                                }
                            }
                            else
                            {
                                logger.LogWarning(
                                    "There was no active media announcement for a media format parameter attribute, ignoring.");
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("a=" + ICE_CANDIDATE_ATTRIBUTE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                if (activeAnnouncement.IceCandidates == null)
                                {
                                    activeAnnouncement.IceCandidates = new List <string>();
                                }

                                activeAnnouncement.IceCandidates.Add(
                                    sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1));
                            }
                            else
                            {
                                if (sdp.IceCandidates == null)
                                {
                                    sdp.IceCandidates = new List <string>();
                                }

                                sdp.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1));
                            }
                        }
                        //2018-12-21 rj2: add a=crypto
                        else if (sdpLineTrimmed.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                try
                                {
                                    activeAnnouncement.AddCryptoLine(sdpLineTrimmed);
                                }
                                catch (FormatException fex)
                                {
                                    logger.LogWarning("Error Parsing SDP-Line(a=crypto) " + fex);
                                }
                            }
                        }
                        else if (MediaStreamStatusType.IsMediaStreamStatusAttribute(sdpLine.Trim(),
                                                                                    out var mediaStreamStatus))
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.MediaStreamStatus = mediaStreamStatus;
                            }
                            else
                            {
                                sdp.SessionMediaStreamStatus = mediaStreamStatus;
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith("a=" + MEDIA_ID_ATTRIBUTE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.MediaID = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1);
                            }
                            else
                            {
                                logger.LogWarning("A media ID can only be set on a media announcement.");
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX)
                                 )
                        {
                            if (activeAnnouncement != null)
                            {
                                string[] fields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' ');

                                // Set the ID.
                                if (fields.Length > 0)
                                {
                                    activeAnnouncement.SsrcGroupID = fields[0];
                                }

                                // Add attributes for each of the SSRC values.
                                for (int i = 1; i < fields.Length; i++)
                                {
                                    if (uint.TryParse(fields[i], out var ssrc))
                                    {
                                        activeAnnouncement.SsrcAttributes.Add(
                                            new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID));
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("A ssrc-group ID can only be set on a media announcement.");
                            }
                        }
                        else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX))
                        {
                            if (activeAnnouncement != null)
                            {
                                string[] ssrcFields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)
                                                      .Split(' ');

                                if (ssrcFields.Length > 0 && uint.TryParse(ssrcFields[0], out var ssrc))
                                {
                                    var ssrcAttribute =
                                        activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc);
                                    if (ssrcAttribute == null)
                                    {
                                        ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null);
                                        activeAnnouncement.SsrcAttributes.Add(ssrcAttribute);
                                    }

                                    if (ssrcFields.Length > 1)
                                    {
                                        if (ssrcFields[1].StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX))
                                        {
                                            ssrcAttribute.Cname =
                                                ssrcFields[1].Substring(ssrcFields[1].IndexOf(':') + 1);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                logger.LogWarning("A ssrc attribute can only be set on a media announcement.");
                            }
                        }
                        else
                        {
                            if (activeAnnouncement != null)
                            {
                                activeAnnouncement.AddExtra(sdpLineTrimmed);
                            }
                            else
                            {
                                sdp.AddExtra(sdpLineTrimmed);
                            }
                        }
                    }

                    return(sdp);
                }
                else
                {
                    return(null);
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception ParseSDPDescription. " + excp.Message);
                throw excp;
            }
        }
示例#33
0
        /// <summary>
        /// Generates the SDP for an offer or answer.
        /// </summary>
        private async Task <SDP> getSdp(string setupAttribute)
        {
            try
            {
                DateTime startGatheringTime = DateTime.Now;

                IceConnectionState = IceConnectionStatesEnum.Gathering;

                await GetIceCandidatesAsync();

                logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}.");

                IceConnectionState = IceConnectionStatesEnum.GatheringComplete;

                if (LocalIceCandidates.Count == 0)
                {
                    //logger.LogWarning("No local socket candidates were found for WebRTC call closing.");
                    //Close("No local ICE candidates available.");
                    throw new ApplicationException("No local ICE candidates available.");
                }
                else
                {
                    bool haveIceCandidatesBeenAdded = false;

                    string localIceCandidateString = null;

                    foreach (var iceCandidate in LocalIceCandidates)
                    {
                        localIceCandidateString += iceCandidate.ToString();
                    }

                    LocalIceUser     = LocalIceUser ?? Crypto.GetRandomString(20);
                    LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20);

                    SDP offerSdp = new SDP(IPAddress.Loopback);
                    offerSdp.SessionId = Crypto.GetRandomInt(5).ToString();

                    // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed
                    // on a single RTP socket.
                    if (RtpSession.AudioTrack != null && RtpSession.VideoTrack != null)
                    {
                        offerSdp.Group = MEDIA_GROUPING;
                    }

                    // --- Audio announcement ---
                    if (RtpSession.AudioTrack != null)
                    {
                        SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.audio,
                            _rtpChannel.RTPPort,
                            RtpSession.AudioTrack.Capabilties);

                        audioAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            audioAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        audioAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        audioAnnouncement.IceUfrag        = LocalIceUser;
                        audioAnnouncement.IcePwd          = LocalIcePassword;
                        audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        audioAnnouncement.AddExtra(setupAttribute);
                        audioAnnouncement.MediaStreamStatus = RtpSession.AudioTrack.Transceiver.Direction;
                        audioAnnouncement.MediaID           = RtpSession.AudioTrack.Transceiver.MID;

                        offerSdp.Media.Add(audioAnnouncement);
                    }

                    // --- Video announcement ---
                    if (RtpSession.VideoTrack != null)
                    {
                        SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.video,
                            _rtpChannel.RTPPort,
                            RtpSession.VideoTrack.Capabilties);

                        videoAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            videoAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        videoAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        videoAnnouncement.IceUfrag        = LocalIceUser;
                        videoAnnouncement.IcePwd          = LocalIcePassword;
                        videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        videoAnnouncement.AddExtra(setupAttribute);
                        videoAnnouncement.MediaStreamStatus = RtpSession.VideoTrack.Transceiver.Direction;
                        videoAnnouncement.MediaID           = RtpSession.VideoTrack.Transceiver.MID;;

                        offerSdp.Media.Add(videoAnnouncement);
                    }

                    OnSdpOfferReady?.Invoke(offerSdp);

                    return(offerSdp);
                }

                // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check.
                // If there are no remote candidates this call will end up being a NOP.
                //SendStunConnectivityChecks(null);

                //if (_doDtlsHandshake != null)
                //{
                //    _ = Task.Run(() =>
                //    {
                //        int result = _doDtlsHandshake(this);
                //        IsDtlsNegotiationComplete = (result == 0);
                //    });
                //}
            }
            catch (Exception excp)
            {
                logger.LogError("Exception getSdp. " + excp);
                Close(excp.Message);

                throw;
            }
        }
示例#34
0
        public void SetRemoteSDP(SDP sdp)
        {
            _remoteSDP = sdp;

            IPAddress remoteRTPIPAddress = IPAddress.Parse(sdp.Connection.ConnectionAddress);
            int remoteAudioPort = 0;
            int remoteVideoPort = 0;

            var audioAnnouncement = sdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault();
            if (audioAnnouncement != null)
            {
                remoteAudioPort = audioAnnouncement.Port;
            }

            var videoAnnouncement = sdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault();
            if (videoAnnouncement != null)
            {
                remoteVideoPort = videoAnnouncement.Port;
            }

            if (remoteAudioPort != 0)
            {
                _remoteAudioEP = new IPEndPoint(remoteRTPIPAddress, remoteAudioPort);
                logger.Debug("RTP channel remote end point set to audio socket of " + _remoteAudioEP + ".");
                _rtpAudioChannel.RemoteEndPoint = _remoteAudioEP;
                _rtpAudioChannel.Start();
            }

            if (remoteVideoPort != 0)
            {
                _remoteVideoEP = new IPEndPoint(remoteRTPIPAddress, remoteVideoPort);
                logger.Debug("RTP channel remote end point set to video socket of " + _remoteVideoEP + ".");
                _rtpVideoChannel.RemoteEndPoint = new IPEndPoint(remoteRTPIPAddress, remoteVideoPort);
                _rtpVideoChannel.Start();
            }

            if (remoteAudioPort == 0 && remoteVideoPort == 0)
            {
                logger.Warn("No audio or video end point could be extracted from the remote SDP.");
            }
        }
示例#35
0
        /// <summary>
        /// Updates the session after receiving the remote SDP.
        /// At this point check that the codecs match. We currently only support:
        ///  - Audio: PCMU,
        ///  - Video: VP8.
        /// If they are not available there's no point carrying on.
        /// </summary>
        /// <param name="sessionDescription">The answer/offer SDP from the remote party.</param>
        public SetDescriptionResultEnum setRemoteDescription(RTCSessionDescriptionInit init)
        {
            RTCSessionDescription description = new RTCSessionDescription {
                type = init.type, sdp = SDP.ParseSDPDescription(init.sdp)
            };

            remoteDescription = description;

            SDP remoteSdp = SDP.ParseSDPDescription(init.sdp);

            SdpType sdpType   = (init.type == RTCSdpType.offer) ? SdpType.offer : SdpType.answer;
            var     setResult = base.SetRemoteDescription(sdpType, remoteSdp);

            if (setResult == SetDescriptionResultEnum.OK)
            {
                string remoteIceUser     = remoteSdp.IceUfrag;
                string remoteIcePassword = remoteSdp.IcePwd;
                string dtlsFingerprint   = remoteSdp.DtlsFingerprint;

                var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault();
                if (audioAnnounce != null)
                {
                    remoteIceUser     = remoteIceUser ?? audioAnnounce.IceUfrag;
                    remoteIcePassword = remoteIcePassword ?? audioAnnounce.IcePwd;
                    dtlsFingerprint   = dtlsFingerprint ?? audioAnnounce.DtlsFingerprint;
                }

                var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault();
                if (videoAnnounce != null)
                {
                    if (remoteIceUser == null || remoteIcePassword == null || dtlsFingerprint == null)
                    {
                        remoteIceUser     = remoteIceUser ?? videoAnnounce.IceUfrag;
                        remoteIcePassword = remoteIcePassword ?? videoAnnounce.IcePwd;
                        dtlsFingerprint   = dtlsFingerprint ?? videoAnnounce.DtlsFingerprint;
                    }
                }

                SdpSessionID = remoteSdp.SessionId;

                if (init.type == RTCSdpType.answer)
                {
                    _rtpIceChannel.IsController = true;
                    // Set DTLS role to be server.
                    IceRole = IceRolesEnum.passive;
                }
                else
                {
                    // Set DTLS role as client.
                    IceRole = IceRolesEnum.active;
                }

                if (remoteIceUser != null && remoteIcePassword != null)
                {
                    _rtpIceChannel.SetRemoteCredentials(remoteIceUser, remoteIcePassword);
                }

                if (!string.IsNullOrWhiteSpace(dtlsFingerprint))
                {
                    dtlsFingerprint = dtlsFingerprint.Trim().ToLower();
                    if (RTCDtlsFingerprint.TryParse(dtlsFingerprint, out var remoteFingerprint))
                    {
                        RemotePeerDtlsFingerprint = remoteFingerprint;
                    }
                    else
                    {
                        logger.LogWarning($"The DTLS fingerprint was invalid or not supported.");
                        return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported);
                    }
                }
                else
                {
                    logger.LogWarning("The DTLS fingerprint was missing from the remote party's session description.");
                    return(SetDescriptionResultEnum.DtlsFingerprintMissing);
                }

                // All browsers seem to have gone to trickling ICE candidates now but just
                // in case one or more are given we can start the STUN dance immediately.
                if (remoteSdp.IceCandidates != null)
                {
                    foreach (var iceCandidate in remoteSdp.IceCandidates)
                    {
                        addIceCandidate(new RTCIceCandidateInit {
                            candidate = iceCandidate
                        });
                    }
                }

                foreach (var media in remoteSdp.Media)
                {
                    if (media.IceCandidates != null)
                    {
                        foreach (var iceCandidate in media.IceCandidates)
                        {
                            addIceCandidate(new RTCIceCandidateInit {
                                candidate = iceCandidate
                            });
                        }
                    }
                }

                signalingState = RTCSignalingState.have_remote_offer;
                onsignalingstatechange?.Invoke();
            }

            return(setResult);
        }