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); }
public void AddMulitpleIceServersTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); var iceServers = new List <RTCIceServer> { new RTCIceServer { urls = $"stun:127.0.0.1:3478,stun:[::1]:3478" }, new RTCIceServer { urls = $"turn:turn:127.0.0.1:3478,stun:[::1]:3479" }, }; var rtpIceChannel = new RtpIceChannel(null, RTCIceComponent.rtp, iceServers); logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannel.RTPLocalEndPoint}."); foreach (var pair in rtpIceChannel._iceServerConnections) { logger.LogDebug($"ICE server {pair.Key}, tx ID {pair.Value._id}"); Assert.Equal(1, rtpIceChannel._iceServerConnections.Values.Count(x => x._id == pair.Value._id)); } }
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); }
public void SortChecklistUnitTest() { 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(hostCandidate.ToString()); } 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); var remoteCandidate2 = RTCIceCandidate.Parse("candidate:408132417 1 udp 2113937150 192.168.11.51 51268 typ host generation 0 ufrag CI7o network-cost 999"); rtpIceChannel.AddRemoteCandidate(remoteCandidate2); foreach (var entry in rtpIceChannel._checklist) { logger.LogDebug($"checklist entry priority {entry.Priority}."); } }
public async void ChecklistConstructionUnitTest() { 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); var remoteCandidate2 = RTCIceCandidate.Parse("candidate:408132417 1 udp 2113937150 192.168.11.50 51268 typ host generation 0 ufrag CI7o network-cost 999"); rtpIceChannel.AddRemoteCandidate(remoteCandidate2); await Task.Delay(500); foreach (var entry in rtpIceChannel._checklist) { logger.LogDebug($"checklist entry: {entry.LocalCandidate.ToShortString()}->{entry.RemoteCandidate.ToShortString()}"); } Assert.Single(rtpIceChannel._checklist); }
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); }
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); } }
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); }
/// <summary> /// Creates a new RTP ICE channel (which manages the UDP socket sending and receiving RTP /// packets) for use with this session. /// </summary> /// <param name="mediaType">The type of media the RTP channel is for. Must be audio or video.</param> /// <returns>A new RTPChannel instance.</returns> protected override RTPChannel CreateRtpChannel(SDPMediaTypesEnum mediaType) { var rtpIceChannel = new RtpIceChannel( _configuration?.X_BindAddress, RTCIceComponent.rtp, _configuration?.iceServers, _configuration != null ? _configuration.iceTransportPolicy : RTCIceTransportPolicy.all); m_rtpChannels.Add(mediaType, rtpIceChannel); rtpIceChannel.OnRTPDataReceived += OnRTPDataReceived; // Start the RTP, and if required the Control, socket receivers and the RTCP session. rtpIceChannel.Start(); return(rtpIceChannel); }
public async void CheckIPAddressOnlyStunServerUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); using (MockTurnServer mockStunServer = new MockTurnServer()) { // Give the TURN server socket receive tasks time to fire up. await Task.Delay(1000); var iceServers = new List <RTCIceServer> { new RTCIceServer { urls = $"stun:{mockStunServer.ListeningEndPoint}", } }; var rtpIceChannel = new RtpIceChannel(null, RTCIceComponent.rtp, iceServers); logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannel.RTPLocalEndPoint}."); ManualResetEventSlim gatheringCompleted = new ManualResetEventSlim(); rtpIceChannel.OnIceGatheringStateChange += (state) => { if (state == RTCIceGatheringState.complete) { gatheringCompleted.Set(); } }; rtpIceChannel.StartGathering(); Assert.NotEmpty(rtpIceChannel.Candidates); // Because there is an ICE server gathering should still be in progress. Assert.Equal(RTCIceGatheringState.gathering, rtpIceChannel.IceGatheringState); Assert.Equal(RTCIceConnectionState.@new, rtpIceChannel.IceConnectionState); Assert.True(gatheringCompleted.Wait(3000)); // The STUN server check should now have completed and a server reflexive candidate // been acquired Assert.Equal(RTCIceGatheringState.complete, rtpIceChannel.IceGatheringState); // The connection state stays in "new" because no remote ICE user and password has been set. Assert.Equal(RTCIceConnectionState.@new, rtpIceChannel.IceConnectionState); Assert.Contains(rtpIceChannel.Candidates, x => x.type == RTCIceCandidateType.srflx); } }
public void CreateInstanceUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); RTPSession rtpSession = new RTPSession(true, true, true); // Add a track to the session in order to initialise the RTPChannel. MediaStreamTrack dummyTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, new List <SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU) }); rtpSession.addTrack(dummyTrack); var rtpIceChannel = new RtpIceChannel(null, RTCIceComponent.rtp, null); Assert.NotNull(rtpIceChannel); }
public void GetHostCandidatesUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); var rtpIceChannel = new RtpIceChannel(null, RTCIceComponent.rtp, null); logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannel.RTPLocalEndPoint}."); rtpIceChannel.StartGathering(); Assert.NotNull(rtpIceChannel); Assert.NotEmpty(rtpIceChannel.Candidates); foreach (var hostCandidate in rtpIceChannel.Candidates) { logger.LogDebug(hostCandidate.ToString()); } }
public void GetHostCandidatesForRTPBindUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); var localAddress = NetServices.InternetDefaultAddress; var rtpIceChannel = new RtpIceChannel(localAddress, RTCIceComponent.rtp, null); logger.LogDebug($"RTP ICE channel RTP socket local end point {rtpIceChannel.RTPLocalEndPoint}."); rtpIceChannel.StartGathering(); Assert.NotNull(rtpIceChannel); Assert.NotEmpty(rtpIceChannel.Candidates); Assert.True(localAddress.Equals(IPAddress.Parse(rtpIceChannel.Candidates.Single().address))); foreach (var hostCandidate in rtpIceChannel.Candidates) { logger.LogDebug(hostCandidate.ToString()); } }
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); }
/// <summary> /// Constructor to create a new RTC peer connection instance. /// </summary> /// <param name="configuration">Optional.</param> public RTCPeerConnection(RTCConfiguration configuration) : base(true, true, true, configuration?.X_BindAddress) { if (_configuration != null && _configuration.iceTransportPolicy == RTCIceTransportPolicy.relay && _configuration.iceServers?.Count == 0) { throw new ApplicationException("RTCPeerConnection must have at least one ICE server specified for a relay only transport policy."); } _configuration = configuration; if (_configuration != null) { if (_configuration.certificates?.Count > 0) { _currentCertificate = _configuration.certificates.First(); } if (_configuration.X_UseRtpFeedbackProfile) { RTP_MEDIA_PROFILE = RTP_MEDIA_FEEDBACK_PROFILE; } } SessionID = Guid.NewGuid().ToString(); LocalSdpSessionID = Crypto.GetRandomInt(5).ToString(); // Request the underlying RTP session to create a single RTP channel that will // be used to multiplex all required media streams. addSingleTrack(); _rtpIceChannel = GetRtpChannel(SDPMediaTypesEnum.audio) as RtpIceChannel; _rtpIceChannel.OnIceCandidate += (candidate) => onicecandidate?.Invoke(candidate); _rtpIceChannel.OnIceConnectionStateChange += (state) => { if (state == RTCIceConnectionState.connected && _rtpIceChannel.NominatedEntry != null) { var connectedEP = _rtpIceChannel.NominatedEntry.RemoteCandidate.DestinationEndPoint; base.SetDestination(SDPMediaTypesEnum.audio, connectedEP, connectedEP); } iceConnectionState = state; oniceconnectionstatechange?.Invoke(iceConnectionState); if (base.IsSecureContextReady && iceConnectionState == RTCIceConnectionState.connected && connectionState != RTCPeerConnectionState.connected) { // This is the case where the ICE connection checks completed after the DTLS handshake. connectionState = RTCPeerConnectionState.connected; onconnectionstatechange?.Invoke(RTCPeerConnectionState.connected); } }; _rtpIceChannel.OnIceGatheringStateChange += (state) => onicegatheringstatechange?.Invoke(state); _rtpIceChannel.OnIceCandidateError += (candidate, error) => onicecandidateerror?.Invoke(candidate, error); OnRtpClosed += Close; OnRtcpBye += Close; onnegotiationneeded?.Invoke(); _rtpIceChannel.StartGathering(); }
/// <summary> /// Constructor to create a new RTC peer connection instance. /// </summary> /// <param name="configuration">Optional.</param> public RTCPeerConnection(RTCConfiguration configuration) : base(true, true, true, configuration?.X_BindAddress) { if (_configuration != null && _configuration.iceTransportPolicy == RTCIceTransportPolicy.relay && _configuration.iceServers?.Count == 0) { throw new ApplicationException("RTCPeerConnection must have at least one ICE server specified for a relay only transport policy."); } if (configuration != null) { _configuration = configuration; if (_configuration.certificates?.Count > 0) { // Find the first certificate that has a usable private key. RTCCertificate usableCert = null; foreach (var cert in _configuration.certificates) { // Attempting to check that a certificate has an exportable private key. // TODO: Does not seem to be a particularly reliable way of checking private key exportability. if (cert.Certificate.HasPrivateKey) { //if (cert.Certificate.PrivateKey is RSACryptoServiceProvider) //{ // var rsa = cert.Certificate.PrivateKey as RSACryptoServiceProvider; // if (!rsa.CspKeyContainerInfo.Exportable) // { // logger.LogWarning($"RTCPeerConnection was passed a certificate for {cert.Certificate.FriendlyName} with a non-exportable RSA private key."); // } // else // { // usableCert = cert; // break; // } //} //else //{ usableCert = cert; break; //} } } if (usableCert == null) { throw new ApplicationException("RTCPeerConnection was not able to find a certificate from the input configuration list with a usable private key."); } else { _currentCertificate = usableCert; } } if (_configuration.X_UseRtpFeedbackProfile) { RTP_MEDIA_PROFILE = RTP_MEDIA_FEEDBACK_PROFILE; } } else { _configuration = new RTCConfiguration(); } // No certificate was provided so create a new self signed one. if (_configuration.certificates == null || _configuration.certificates.Count == 0) { _currentCertificate = new RTCCertificate { Certificate = DtlsUtils.CreateSelfSignedCert() }; _configuration.certificates = new List <RTCCertificate> { _currentCertificate }; } SessionID = Guid.NewGuid().ToString(); LocalSdpSessionID = Crypto.GetRandomInt(5).ToString(); // Request the underlying RTP session to create a single RTP channel that will // be used to multiplex all required media streams. addSingleTrack(); _rtpIceChannel = GetRtpChannel(); _rtpIceChannel.OnIceCandidate += (candidate) => onicecandidate?.Invoke(candidate); _rtpIceChannel.OnIceConnectionStateChange += (state) => { if (state == RTCIceConnectionState.connected && _rtpIceChannel.NominatedEntry != null) { var connectedEP = _rtpIceChannel.NominatedEntry.RemoteCandidate.DestinationEndPoint; base.SetDestination(SDPMediaTypesEnum.audio, connectedEP, connectedEP); logger.LogInformation($"ICE connected to remote end point {AudioDestinationEndPoint}."); DtlsSrtpTransport dtlsHandle = new DtlsSrtpTransport( IceRole == IceRolesEnum.active ? (IDtlsSrtpPeer) new DtlsSrtpClient(_currentCertificate.Certificate) : (IDtlsSrtpPeer) new DtlsSrtpServer(_currentCertificate.Certificate)); OnDtlsPacket += (buf) => { logger.LogDebug($"DTLS transport received {buf.Length} bytes from {AudioDestinationEndPoint}."); dtlsHandle.WriteToRecvStream(buf); }; logger.LogDebug($"Starting DLS handshake with role {IceRole}."); Task.Run <bool>(() => DoDtlsHandshake(dtlsHandle)) .ContinueWith(t => { if (t.IsFaulted) { logger.LogWarning($"RTCPeerConnection DTLS handshake task completed in a faulted state. {t.Exception?.Flatten().Message}"); connectionState = RTCPeerConnectionState.failed; onconnectionstatechange?.Invoke(connectionState); } else { connectionState = (t.Result) ? RTCPeerConnectionState.connected : connectionState = RTCPeerConnectionState.failed; onconnectionstatechange?.Invoke(connectionState); } }); } iceConnectionState = state; oniceconnectionstatechange?.Invoke(iceConnectionState); }; _rtpIceChannel.OnIceGatheringStateChange += (state) => onicegatheringstatechange?.Invoke(state); _rtpIceChannel.OnIceCandidateError += (candidate, error) => onicecandidateerror?.Invoke(candidate, error); OnRtpClosed += Close; OnRtcpBye += Close; onnegotiationneeded?.Invoke(); _rtpIceChannel.StartGathering(); }