Example #1
0
 /// <summary>
 /// Sets the nominated checklist entry. This action completes the checklist processing and
 /// indicates the connection checks were successful.
 /// </summary>
 /// <param name="entry">The checklist entry that was nominated.</param>
 private void SetNominatedEntry(ChecklistEntry entry)
 {
     entry.Nominated    = true;
     _checklistState    = ChecklistState.Completed;
     NominatedCandidate = entry.RemoteCandidate;
     ConnectionState    = RTCIceConnectionState.connected;
     OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.connected);
 }
Example #2
0
        /// <summary>
        /// We've been given the green light to start the ICE candidate gathering process.
        /// This could include contacting external STUN and TURN servers. Events will
        /// be fired as each ICE is identified and as the gathering state machine changes
        /// state.
        /// </summary>
        public void StartGathering()
        {
            GatheringState = RTCIceGatheringState.gathering;
            OnIceGatheringStateChange?.Invoke(RTCIceGatheringState.gathering);
            ConnectionState = RTCIceConnectionState.checking;
            OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.checking);

            _stunChecksTimer = new Timer(SendStunConnectivityChecks, null, 0, Ta);
        }
Example #3
0
        public void SetRemoteCredentials(string username, string password)
        {
            RemoteIceUser     = username;
            RemoteIcePassword = password;

            // Once the remote party's ICE credentials are known connection checking can
            // commence immediately as candidates trickle in.
            ConnectionState = RTCIceConnectionState.checking;
            OnIceConnectionStateChange?.Invoke(ConnectionState);
        }
Example #4
0
 private RTCPeerConnection(IJSRuntime jsRuntime, JsObjectRef jsObjectRef, RTCConfiguration rtcConfiguration)
     : base(jsRuntime, jsObjectRef)
 {
     AddNativeEventListener("connectionstatechange", (s, e) => OnConnectionStateChanged?.Invoke(s, e));
     AddNativeEventListenerForObjectRef("datachannel", (s, e) => OnDataChannel?.Invoke(s, e),
                                        RTCDataChannelEvent.Create);
     AddNativeEventListenerForObjectRef("icecandidate", (s, e) => OnIceCandidate?.Invoke(s, e),
                                        RTCPeerConnectionIceEvent.Create);
     AddNativeEventListener("iceconnectionstatechange", (s, e) => OnIceConnectionStateChange?.Invoke(s, e));
     AddNativeEventListener("icegatheringstatechange", (s, e) => OnIceGatheringStateChange?.Invoke(s, e));
     AddNativeEventListener("negotiationneeded", (s, e) => OnNegotiationNeeded?.Invoke(s, e));
     AddNativeEventListener("signallingstatechange", (s, e) => OnSignallingStateChange?.Invoke(s, e));
     AddNativeEventListenerForObjectRef("track", (s, e) => OnTrack?.Invoke(s, e),
                                        RTCTrackEvent.Create);
 }
Example #5
0
        public void DidChangeIceConnectionState(Webrtc.RTCPeerConnection peerConnection,
                                                Webrtc.RTCIceConnectionState newState)
        {
            //System.Diagnostics.Debug.WriteLine($"OOOOOOOOOOOOOOOOOOOOOOO PeerConnection.IceConnectionState: {newState}");

            OnIceConnectionStateChange?.Invoke(this, EventArgs.Empty);

            //// Make sure that state is connected with several attempts.
            //if (newState == Webrtc.RTCIceConnectionState.Connected)
            //{
            //    Timer timer = null;
            //    int count = 5;


            //    // Make sure that state is connected with several attempts.
            //    timer = new Timer(new TimerCallback((state) =>
            //    {
            //        if (((Webrtc.RTCPeerConnection)NativeObject).ConnectionState ==
            //            Webrtc.RTCPeerConnectionState.Connected || --count == 0)
            //        {
            //            timer.Dispose();
            //            System.Diagnostics.Debug.WriteLine($"OOOOOOOOOOOOOOOOOOOOOOO PeerConnection GENERATED CONNECTED {count}");
            //            OnConnectionStateChanged?.Invoke(this, EventArgs.Empty);
            //            return;
            //        }
            //    }), null, 10, 50);
            //    return;
            //}
            //else if (newState == Webrtc.RTCIceConnectionState.Disconnected)
            //{
            //    Timer timer = null;
            //    int count = 5;

            //    // Make sure that state is disconnected with several attempsts.
            //    timer = new Timer(new TimerCallback((state) =>
            //    {
            //        if (((Webrtc.RTCPeerConnection)NativeObject).ConnectionState ==
            //            Webrtc.RTCPeerConnectionState.Disconnected || --count == 0)
            //        {
            //            timer.Dispose();
            //            OnConnectionStateChanged?.Invoke(this, EventArgs.Empty);
            //            return;
            //        }
            //    }), null, 10, 50);
            //    return;
            //}
        }
Example #6
0
        public void OnIceConnectionChange(Webrtc.PeerConnection.IceConnectionState p0)
        {
            OnIceConnectionStateChange?.Invoke(this, EventArgs.Empty);

            // !!! I don't know why Android DOES NOT provide Connection State Change event???
            // I drive this event from Ice Connection State Change event here for now.

#if false
            if (p0 == Webrtc.PeerConnection.IceConnectionState.New)
            {
            }
            else if (p0 == Webrtc.PeerConnection.IceConnectionState.Checking)
            {
            }
            else if (p0 == Webrtc.PeerConnection.IceConnectionState.Connected)
            {
                Timer timer = null;
                int   count = 5;

                // Make sure that state is connected with several attempts.
                timer = new Timer(new TimerCallback((state) =>
                {
                    if (((Webrtc.PeerConnection)NativeObject).ConnectionState() ==
                        Webrtc.PeerConnection.PeerConnectionState.Connected || --count == 0)
                    {
                        timer.Dispose();
                        System.Diagnostics.Debug.WriteLine($"OOOOOOOOOOOOOOOOOOOOOOO PeerConnection GENERATED CONNECTED {count}");
                        OnConnectionStateChanged?.Invoke(this, EventArgs.Empty);
                        return;
                    }
                }), null, 10, 50);
                return;
            }
            else if (p0 == Webrtc.PeerConnection.IceConnectionState.Completed)
            {
            }
            else if (p0 == Webrtc.PeerConnection.IceConnectionState.Failed)
            {
            }
            else if (p0 == Webrtc.PeerConnection.IceConnectionState.Disconnected)
            {
                Timer timer = null;
                int   count = 5;

                // Make sure that state is disconnected with several attempsts.
                timer = new Timer(new TimerCallback((state) =>
                {
                    if (((Webrtc.PeerConnection)NativeObject).ConnectionState() ==
                        Webrtc.PeerConnection.PeerConnectionState.Disconnected || --count == 0)
                    {
                        timer.Dispose();
                        OnConnectionStateChanged?.Invoke(this, EventArgs.Empty);
                        return;
                    }
                }), null, 10, 50);

                return;
            }
            else if (p0 == Webrtc.PeerConnection.IceConnectionState.Closed)
            {
            }
#endif

            if (p0 == PeerConnection.IceConnectionState.Connected || p0 == PeerConnection.IceConnectionState.Completed)
            {
                if (!_isConnected)
                {
                    _isConnected = true;
                    OnConnectionStateChanged?.Invoke(this, EventArgs.Empty);
                }
            }
            else if (_isConnected)
            {
                _isConnected = false;
                OnConnectionStateChanged?.Invoke(this, EventArgs.Empty);
            }
        }
Example #7
0
        /// <summary>
        /// Attempts to get a list of local ICE candidates.
        /// </summary>
        //private async Task GetIceCandidatesAsync()
        //{
        //    // The media is being multiplexed so the audio and video RTP channel is the same.
        //    var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio);

        //    if (rtpChannel == null)
        //    {
        //        throw new ApplicationException("Cannot start gathering ICE candidates without an RTP channel.");
        //    }
        //    else
        //    {
        //        var localIPAddresses = _offerAddresses ?? NetServices.GetAllLocalIPAddresses();
        //        IceNegotiationStartedAt = DateTime.Now;
        //        LocalIceCandidates = new List<IceCandidate>();

        //        foreach (var address in localIPAddresses.Where(x => x.AddressFamily == rtpChannel.RTPLocalEndPoint.AddressFamily))
        //        {
        //            var iceCandidate = new IceCandidate(address, (ushort)rtpChannel.RTPPort);

        //            if (_turnServerEndPoint != null)
        //            {
        //                iceCandidate.TurnServer = new TurnServer() { ServerEndPoint = _turnServerEndPoint };
        //                iceCandidate.InitialStunBindingCheck = SendTurnServerBindingRequest(iceCandidate);
        //            }

        //            LocalIceCandidates.Add(iceCandidate);
        //        }

        //        await Task.WhenAll(LocalIceCandidates.Where(x => x.InitialStunBindingCheck != null).Select(x => x.InitialStunBindingCheck)).ConfigureAwait(false);
        //    }
        //}

        public void ProcessStunMessage(STUNv2Message stunMessage, IPEndPoint receivedOn)
        {
            IPEndPoint remoteEndPoint = (!receivedOn.Address.IsIPv4MappedToIPv6) ? receivedOn : new IPEndPoint(receivedOn.Address.MapToIPv4(), receivedOn.Port);

            //logger.LogDebug($"STUN message received from remote {remoteEndPoint} {stunMessage.Header.MessageType}.");

            if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest)
            {
                STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse);
                stunResponse.Header.TransactionId = stunMessage.Header.TransactionId;
                stunResponse.AddXORMappedAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port);

                // ToDo: Check authentication.

                string localIcePassword = LocalIcePassword;
                byte[] stunRespBytes    = stunResponse.ToByteBufferStringKey(localIcePassword, true);
                //iceCandidate.LocalRtpSocket.SendTo(stunRespBytes, remoteEndPoint);
                _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunRespBytes);

                //iceCandidate.LastStunRequestReceivedAt = DateTime.Now;
                //iceCandidate.IsStunRemoteExchangeComplete = true;

                //if (remoteEndPoint == null)
                //{
                //RemoteEndPoint = remoteEndPoint;
                //SetDestination(SDPMediaTypesEnum.audio, RemoteEndPoint, RemoteEndPoint);
                //OnIceConnected?.Invoke(iceCandidate, remoteEndPoint);
                //IceConnectionState = RTCIceConnectionState.connected;
                //}

                if (_remoteCandidates != null && !_remoteCandidates.Any(x =>
                                                                        (x.address == remoteEndPoint.Address.ToString() || x.relatedAddress == remoteEndPoint.Address.ToString()) &&
                                                                        (x.port == remoteEndPoint.Port || x.relatedPort == remoteEndPoint.Port)))
                {
                    // This STUN request has come from a socket not in the remote ICE candidates list. Add it so we can send our STUN binding request to it.
                    // RTCIceCandidate remoteIceCandidate = new IceCandidate("udp", remoteEndPoint.Address, (ushort)remoteEndPoint.Port, RTCIceCandidateType.host);
                    RTCIceCandidate peerRflxCandidate = new RTCIceCandidate(new RTCIceCandidateInit());
                    peerRflxCandidate.SetAddressProperties(RTCIceProtocol.udp, remoteEndPoint.Address, (ushort)remoteEndPoint.Port, RTCIceCandidateType.prflx, null, 0);
                    logger.LogDebug($"Adding peer reflex ICE candidate for {remoteEndPoint}.");
                    _remoteCandidates.Add(peerRflxCandidate);

                    // Some browsers require a STUN binding request from our end before the DTLS handshake will be initiated.
                    // The STUN connectivity checks are already scheduled but we can speed things up by sending a binding
                    // request immediately.
                    SendStunConnectivityChecks(null);
                }
            }
            else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse)
            {
                if (ConnectionState != RTCIceConnectionState.connected)
                {
                    logger.LogDebug($"ICE session setting connected remote end point to {remoteEndPoint}.");

                    _connectedRemoteEndPoint = remoteEndPoint;

                    ConnectionState = RTCIceConnectionState.connected;
                    OnIceConnectionStateChange?.Invoke(RTCIceConnectionState.connected);
                }

                // TODO: What needs to be done here?

                //if (_turnServerEndPoint != null && remoteEndPoint.ToString() == _turnServerEndPoint.ToString())
                //{
                //    if (iceCandidate.IsGatheringComplete == false)
                //    {
                //        var reflexAddressAttribute = stunMessage.Attributes.FirstOrDefault(y => y.AttributeType == STUNv2AttributeTypesEnum.XORMappedAddress) as STUNv2XORAddressAttribute;

                //        if (reflexAddressAttribute != null)
                //        {
                //            iceCandidate.StunRflxIPEndPoint = new IPEndPoint(reflexAddressAttribute.Address, reflexAddressAttribute.Port);
                //            iceCandidate.IsGatheringComplete = true;

                //            logger.LogDebug("ICE gathering complete for local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", rflx address " + iceCandidate.StunRflxIPEndPoint + ".");
                //        }
                //        else
                //        {
                //            iceCandidate.IsGatheringComplete = true;

                //            logger.LogDebug("The STUN binding response received on " + iceCandidate.RtpChannel.RTPLocalEndPoint + " from " + remoteEndPoint + " did not have an XORMappedAddress attribute, rlfx address can not be determined.");
                //        }
                //    }
                //}
                //else
                //{
                //    iceCandidate.LastStunResponseReceivedAt = DateTime.Now;

                //    if (iceCandidate.IsStunLocalExchangeComplete == false)
                //    {
                //        iceCandidate.IsStunLocalExchangeComplete = true;
                //        logger.LogDebug("WebRTC client STUN exchange complete for call " + CallID + ", candidate local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", remote socket " + remoteEndPoint + ".");

                //        SetIceConnectionState(IceConnectionStatesEnum.Connected);
                //    }
                //}
            }
            else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse)
            {
                logger.LogWarning($"A STUN binding error response was received from {remoteEndPoint}.");
            }
            else
            {
                logger.LogWarning($"An unrecognised STUN request was received from {remoteEndPoint}.");
            }
        }
Example #8
0
        /// <summary>
        /// Processes the checklist and sends any required STUN requests to perform connectivity checks.
        /// </summary>
        /// <remarks>
        /// The scheduling mechanism for ICE is specified in https://tools.ietf.org/html/rfc8445#section-6.1.4.
        /// </remarks>
        private void ProcessChecklist(Object stateInfo)
        {
            try
            {
                if (ConnectionState == RTCIceConnectionState.checking && _checklist != null && _checklist.Count > 0)
                {
                    if (RemoteIceUser == null || RemoteIcePassword == null)
                    {
                        logger.LogWarning("ICE session checklist processing cannot occur as either the remote ICE user or password are not set.");
                        ConnectionState = RTCIceConnectionState.failed;
                    }
                    else
                    {
                        lock (_checklist)
                        {
                            // The checklist gets sorted into priority order whenever a remote candidate and its corresponding candidate pairs
                            // are added. At this point it can be relied upon that the checklist is correctly sorted by candidate pair priority.

                            // Do a check for any timed out entries.
                            var failedEntries = _checklist.Where(x => x.State == ChecklistEntryState.InProgress &&
                                                                 DateTime.Now.Subtract(x.LastCheckSentAt).TotalMilliseconds > RTO &&
                                                                 x.ChecksSent >= N).ToList();

                            foreach (var failedEntry in failedEntries)
                            {
                                logger.LogDebug($"Checks for checklist entry have timed out, state being set to failed: {failedEntry.LocalCandidate} -> {failedEntry.RemoteCandidate}.");
                                failedEntry.State = ChecklistEntryState.Failed;
                            }

                            // Move on to checking for  checklist entries that need an initial check sent.
                            var nextEntry = _checklist.Where(x => x.State == ChecklistEntryState.Waiting).FirstOrDefault();

                            if (nextEntry != null)
                            {
                                SendConnectivityCheck(nextEntry, false);
                                return;
                            }

                            // No waiting entries so check for ones requiring a retransmit.
                            var retransmitEntry = _checklist.Where(x => x.State == ChecklistEntryState.InProgress &&
                                                                   DateTime.Now.Subtract(x.LastCheckSentAt).TotalMilliseconds > RTO).FirstOrDefault();

                            if (retransmitEntry != null)
                            {
                                SendConnectivityCheck(retransmitEntry, false);
                                return;
                            }

                            // If this point is reached and all entries are in a failed state then the overall result
                            // of the ICE check is a failure.
                            if (_checklist.All(x => x.State == ChecklistEntryState.Failed))
                            {
                                _stunChecksTimer.Dispose();
                                _checklistState = ChecklistState.Failed;
                                ConnectionState = RTCIceConnectionState.failed;
                                OnIceConnectionStateChange?.Invoke(ConnectionState);
                            }
                        }
                    }
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception ProcessChecklist. " + excp);
            }
        }