Пример #1
0
        public async void ChecklistProcessingToFailStateUnitTest()
        {
            logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
            logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);

            var rtpIceChannel = new RtpIceChannel(null, RTCIceComponent.rtp, null);

            rtpIceChannel.StartGathering();

            Assert.NotNull(rtpIceChannel);
            Assert.NotEmpty(rtpIceChannel.Candidates);

            foreach (var hostCandidate in rtpIceChannel.Candidates)
            {
                logger.LogDebug($"host candidate: {hostCandidate}");
            }

            var remoteCandidate = RTCIceCandidate.Parse("candidate:408132416 1 udp 2113937151 192.168.11.50 51268 typ host generation 0 ufrag CI7o network-cost 999");

            rtpIceChannel.AddRemoteCandidate(remoteCandidate);

            rtpIceChannel.SetRemoteCredentials("CI7o", "xxxxxxxxxxxx");

            logger.LogDebug($"ICE session retry interval {rtpIceChannel.RTO}ms.");

            await Task.Delay(1000);

            rtpIceChannel._checklist.Single().FirstCheckSentAt = DateTime.Now.Subtract(TimeSpan.FromSeconds(RtpIceChannel.FAILED_TIMEOUT_PERIOD));

            await Task.Delay(1000);

            Assert.Equal(ChecklistEntryState.Failed, rtpIceChannel._checklist.Single().State);
            Assert.Equal(ChecklistState.Failed, rtpIceChannel._checklistState);
            Assert.Equal(RTCIceConnectionState.failed, rtpIceChannel.IceConnectionState);
        }
Пример #2
0
        public async void ChecklistProcessingUnitTest()
        {
            logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
            logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);

            var rtpIceChannel = new RtpIceChannel(null, RTCIceComponent.rtp, null);

            rtpIceChannel.StartGathering();

            Assert.NotNull(rtpIceChannel);
            Assert.NotEmpty(rtpIceChannel.Candidates);

            foreach (var hostCandidate in rtpIceChannel.Candidates)
            {
                logger.LogDebug($"host candidate: {hostCandidate}");
            }

            var remoteCandidate = RTCIceCandidate.Parse("candidate:408132416 1 udp 2113937151 192.168.11.50 51268 typ host generation 0 ufrag CI7o network-cost 999");

            rtpIceChannel.AddRemoteCandidate(remoteCandidate);

            rtpIceChannel.SetRemoteCredentials("CI7o", "xxxxxxxxxxxx");
            rtpIceChannel.StartGathering();

            await Task.Delay(2000);

            var checklistEntry = rtpIceChannel._checklist.Single();

            logger.LogDebug($"Checklist entry state {checklistEntry.State}, last check sent at {checklistEntry.LastCheckSentAt}.");

            Assert.Equal(ChecklistEntryState.InProgress, checklistEntry.State);
        }
Пример #3
0
        public async void ChecklistProcessingToFailStateUnitTest()
        {
            logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
            logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);

            var rtpIceChannel = new RtpIceChannel(null, RTCIceComponent.rtp, null);

            rtpIceChannel.StartGathering();

            Assert.NotNull(rtpIceChannel);
            Assert.NotEmpty(rtpIceChannel.Candidates);

            foreach (var hostCandidate in rtpIceChannel.Candidates)
            {
                logger.LogDebug($"host candidate: {hostCandidate}");
            }

            var remoteCandidate = RTCIceCandidate.Parse("candidate:408132416 1 udp 2113937151 192.168.11.50 51268 typ host generation 0 ufrag CI7o network-cost 999");

            rtpIceChannel.AddRemoteCandidate(remoteCandidate);

            rtpIceChannel.SetRemoteCredentials("CI7o", "xxxxxxxxxxxx");

            logger.LogDebug($"ICE session retry interval {rtpIceChannel.RTO}ms.");

            // The defaults are 5 STUN requests and for a checklist with one entry they will be 500ms apart.
            await Task.Delay(4000);

            Assert.Equal(ChecklistEntryState.Failed, rtpIceChannel._checklist.Single().State);
            Assert.Equal(ChecklistState.Failed, rtpIceChannel._checklistState);
            Assert.Equal(RTCIceConnectionState.failed, rtpIceChannel.IceConnectionState);
        }
Пример #4
0
        public async void CheckSuccessfulConnectionForRelayCandidatesUnitTest()
        {
            logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
            logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);

            using (MockTurnServer mockTurnServer = new MockTurnServer())
            {
                var iceServers = new List <RTCIceServer> {
                    new RTCIceServer
                    {
                        urls = $"turn:{mockTurnServer.ListeningEndPoint}",
                    }
                };
                var rtpIceChannelRelay = new RtpIceChannel(null, RTCIceComponent.rtp, iceServers, RTCIceTransportPolicy.relay);
                rtpIceChannelRelay.IsController = true;
                logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannelRelay.RTPLocalEndPoint}.");

                var rtpIceChannelHost = new RtpIceChannel();
                logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannelHost.RTPLocalEndPoint}.");

                rtpIceChannelRelay.StartGathering();
                rtpIceChannelHost.StartGathering();

                // Need to give some time for the relay channel to connect to the mock TURN server.
                await Task.Delay(200);

                Assert.Single(rtpIceChannelRelay.Candidates);   // Should only have the single local relay candidate.
                Assert.NotEmpty(rtpIceChannelHost.Candidates);
                Assert.Equal(RTCIceGatheringState.complete, rtpIceChannelRelay.IceGatheringState);
                Assert.Equal(RTCIceConnectionState.@new, rtpIceChannelRelay.IceConnectionState);
                Assert.Equal(RTCIceGatheringState.complete, rtpIceChannelHost.IceGatheringState);
                Assert.Equal(RTCIceConnectionState.@new, rtpIceChannelHost.IceConnectionState);

                // Exchange ICE user and passwords.
                rtpIceChannelRelay.SetRemoteCredentials(rtpIceChannelHost.LocalIceUser, rtpIceChannelHost.LocalIcePassword);
                rtpIceChannelHost.SetRemoteCredentials(rtpIceChannelRelay.LocalIceUser, rtpIceChannelRelay.LocalIcePassword);

                Assert.Equal(RTCIceConnectionState.checking, rtpIceChannelRelay.IceConnectionState);
                Assert.Equal(RTCIceConnectionState.checking, rtpIceChannelHost.IceConnectionState);

                // Exchange ICE candidates.
                rtpIceChannelRelay.Candidates.ForEach(x => rtpIceChannelHost.AddRemoteCandidate(x));
                rtpIceChannelHost.Candidates.ForEach(x => rtpIceChannelRelay.AddRemoteCandidate(x));

                await Task.Delay(1000);

                Assert.Equal(RTCIceConnectionState.connected, rtpIceChannelRelay.IceConnectionState);
                Assert.Equal(RTCIceConnectionState.connected, rtpIceChannelHost.IceConnectionState);
                Assert.NotNull(rtpIceChannelRelay.NominatedEntry);
                Assert.NotNull(rtpIceChannelHost.NominatedEntry);
            }
        }
Пример #5
0
        public async void CheckSuccessfulConnectionForHostCandidatesUnitTest()
        {
            logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
            logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);

            var rtpIceChannelA = new RtpIceChannel();

            rtpIceChannelA.IsController = true;
            logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannelA.RTPLocalEndPoint}.");

            var rtpIceChannelB = new RtpIceChannel();

            logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannelB.RTPLocalEndPoint}.");

            rtpIceChannelA.StartGathering();
            rtpIceChannelB.StartGathering();

            Assert.NotEmpty(rtpIceChannelA.Candidates);
            Assert.NotEmpty(rtpIceChannelB.Candidates);

            // Because there are no ICE servers gathering completes after the host candidates are gathered.
            Assert.Equal(RTCIceGatheringState.complete, rtpIceChannelA.IceGatheringState);
            Assert.Equal(RTCIceGatheringState.complete, rtpIceChannelB.IceGatheringState);
            Assert.Equal(RTCIceConnectionState.@new, rtpIceChannelA.IceConnectionState);
            Assert.Equal(RTCIceConnectionState.@new, rtpIceChannelB.IceConnectionState);

            // Exchange ICE user and passwords.
            rtpIceChannelA.SetRemoteCredentials(rtpIceChannelB.LocalIceUser, rtpIceChannelB.LocalIcePassword);
            rtpIceChannelB.SetRemoteCredentials(rtpIceChannelA.LocalIceUser, rtpIceChannelA.LocalIcePassword);

            Assert.Equal(RTCIceConnectionState.checking, rtpIceChannelA.IceConnectionState);
            Assert.Equal(RTCIceConnectionState.checking, rtpIceChannelB.IceConnectionState);

            // Give the RTP channel listeners time to start.
            await Task.Delay(500);

            // Exchange ICE candidates.
            rtpIceChannelA.Candidates.ForEach(x => rtpIceChannelB.AddRemoteCandidate(x));
            rtpIceChannelB.Candidates.ForEach(x => rtpIceChannelA.AddRemoteCandidate(x));

            // Give the RTP ICE channel checklists time to send the first few checks.
            await Task.Delay(4000);

            Assert.Equal(RTCIceConnectionState.connected, rtpIceChannelA.IceConnectionState);
            Assert.Equal(RTCIceConnectionState.connected, rtpIceChannelB.IceConnectionState);
            Assert.NotNull(rtpIceChannelA.NominatedEntry);
            Assert.NotNull(rtpIceChannelB.NominatedEntry);
        }
Пример #6
0
        public async void CheckPeerReflexiveReplacedByHostCandidatesUnitTest()
        {
            logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
            logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);

            var rtpIceChannelA = new RtpIceChannel();

            rtpIceChannelA.IsController = true;
            logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannelA.RTPLocalEndPoint}.");

            var rtpIceChannelB = new RtpIceChannel();

            logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannelB.RTPLocalEndPoint}.");

            // Set up the triggers so the test can proceed at the right pace.
            ManualResetEventSlim connected = new ManualResetEventSlim();

            rtpIceChannelA.OnIceConnectionStateChange += (state) => { if (state == RTCIceConnectionState.connected)
                                                                      {
                                                                          connected.Set();
                                                                      }
            };

            rtpIceChannelA.StartGathering();
            rtpIceChannelB.StartGathering();

            Assert.NotEmpty(rtpIceChannelA.Candidates);
            Assert.NotEmpty(rtpIceChannelB.Candidates);

            // Because there are no ICE servers gathering completes after the host candidates are gathered.
            Assert.Equal(RTCIceGatheringState.complete, rtpIceChannelA.IceGatheringState);
            Assert.Equal(RTCIceGatheringState.complete, rtpIceChannelB.IceGatheringState);
            Assert.Equal(RTCIceConnectionState.@new, rtpIceChannelA.IceConnectionState);
            Assert.Equal(RTCIceConnectionState.@new, rtpIceChannelB.IceConnectionState);

            // Exchange ICE user and passwords.
            //rtpIceChannelA.SetRemoteCredentials(rtpIceChannelB.LocalIceUser, rtpIceChannelB.LocalIcePassword);
            rtpIceChannelB.SetRemoteCredentials(rtpIceChannelA.LocalIceUser, rtpIceChannelA.LocalIcePassword);

            Assert.Equal(RTCIceConnectionState.@new, rtpIceChannelA.IceConnectionState);
            Assert.Equal(RTCIceConnectionState.checking, rtpIceChannelB.IceConnectionState);

            // Only give the non-controlling peer the remote candidates.
            rtpIceChannelA.Candidates.ForEach(x => rtpIceChannelB.AddRemoteCandidate(x));
            //rtpIceChannelB.Candidates.ForEach(x => rtpIceChannelA.AddRemoteCandidate(x));

            // Want channel B to send checks to A so that it create peer reflexive candidates.
            int retries = 0;

            while (rtpIceChannelA._remoteCandidates.Count == 0 && retries < 5)
            {
                logger.LogDebug("Waiting for channel A to acquire peer reflexive candidates.");
                retries++;
                await Task.Delay(500);
            }

            Assert.True(rtpIceChannelA._remoteCandidates.Count > 0);

            logger.LogDebug("Adding remote candidates from B to A.");

            rtpIceChannelB.Candidates.ForEach(x => rtpIceChannelA.AddRemoteCandidate(x));

            // This pause is so that channel A can process the new remote candidates supplied by B.
            // These candidates are host candidates and should replace the peer reflexive candidates
            // that were automatically created previously.
            await Task.Delay(1000);

            logger.LogDebug("Setting remote credentials for channel A.");

            rtpIceChannelA.SetRemoteCredentials(rtpIceChannelB.LocalIceUser, rtpIceChannelB.LocalIcePassword);

            Assert.True(connected.Wait(3000));

            Assert.Equal(RTCIceConnectionState.connected, rtpIceChannelA.IceConnectionState);
            Assert.Equal(RTCIceConnectionState.connected, rtpIceChannelB.IceConnectionState);
            Assert.NotNull(rtpIceChannelA.NominatedEntry);
            Assert.NotNull(rtpIceChannelB.NominatedEntry);
            Assert.Equal(RTCIceCandidateType.host, rtpIceChannelA.NominatedEntry.LocalCandidate.type);
            Assert.Equal(RTCIceCandidateType.host, rtpIceChannelB.NominatedEntry.LocalCandidate.type);
        }
Пример #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="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);
        }
Пример #8
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);
        }