/// <summary> /// Returns at most 2 * <paramref name="k"/> (2 * <paramref name="k"/> + 1 if /// <paramref name="includeTarget"/> is <c>true</c>) nearest peers to given parameter peer /// from routing table. Return value is sorted with respect to target. /// <seealso cref="Kademlia.SortByDistance(IEnumerable{BoundPeer}, Address)"/> /// </summary> /// <param name="target"><see cref="Address"/> to look up.</param> /// <param name="k">Number of peers to return.</param> /// <param name="includeTarget">A boolean value indicates to include a peer with /// <see cref="Address"/> of <paramref name="target"/> in return value or not.</param> /// <returns>An enumerable of <see cref="BoundPeer"/>.</returns> public IEnumerable <BoundPeer> Neighbors(Address target, int k, bool includeTarget) { var sorted = _buckets .Where(b => !b.IsEmpty()) .SelectMany(b => b.Peers) .ToList(); sorted = Kademlia.SortByDistance(sorted, target).ToList(); // Select maximum k * 2 peers excluding the target itself. bool containsTarget = sorted.Any(peer => peer.Address.Equals(target)); int maxCount = (includeTarget && containsTarget) ? k * 2 + 1 : k * 2; IEnumerable <BoundPeer> peers = includeTarget ? sorted : sorted.Where(peer => !peer.Address.Equals(target)); return(peers.Take(maxCount)); }
// returns k nearest peers to given parameter peer from routing table. // return value is already sorted with respect to target. public IEnumerable <BoundPeer> Neighbors(Address target, int k) { var sorted = _buckets .Where(b => !b.IsEmpty()) .SelectMany(b => b.Peers) .ToList(); sorted = Kademlia.SortByDistance(sorted, target); var peers = new List <BoundPeer>(); foreach (var peer in sorted.Where(peer => !peer.Address.Equals(target))) { peers.Add(peer); if (peers.Count >= k * 2) { break; } } return(peers); }
/// <summary> /// Process <see cref="Peer"/>s that is replied by sending <see cref="FindNeighbors"/> /// request. /// </summary> /// <param name="found"><see cref="Peer"/>s that found.</param> /// <param name="target">The target <see cref="Address"/> to search.</param> /// <param name="depth">Target depth of recursive operation. If -1 is given, /// it runs until the closest peer is found.</param> /// <param name="timeout"><see cref="TimeSpan"/> for next depth's /// <see cref="FindPeerAsync"/> operation.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification /// that this operation should be canceled.</param> /// <returns>An awaitable task without value.</returns> /// <exception cref="TimeoutException">Thrown when all peers that found are /// not online.</exception> private async Task ProcessFoundAsync( ImmutableList <BoundPeer> found, Address target, int depth, TimeSpan?timeout, CancellationToken cancellationToken) { List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address) && !_routing.Contains(peer)).ToList(); if (peers.Count == 0) { _logger.Debug("No any neighbor received."); return; } peers = Kademlia.SortByDistance(peers, target); List <BoundPeer> closestCandidate = _routing.Neighbors(target, _bucketSize).ToList(); Task[] awaitables = peers.Select(peer => PingAsync(peer, _requestTimeout, cancellationToken) ).ToArray(); try { await Task.WhenAll(awaitables); } catch (AggregateException e) { if (e.InnerExceptions.All(ie => ie is TimeoutException) && e.InnerExceptions.Count == awaitables.Length) { throw new TimeoutException( $"All neighbors found do not respond in {_requestTimeout}." ); } _logger.Error( e, "Some responses from neighbors found unexpectedly terminated: {Exception}", e ); } var findNeighboursTasks = new List <Task>(); Peer closestKnown = closestCandidate.Count == 0 ? null : closestCandidate[0]; for (int i = 0; i < Kademlia.FindConcurrency && i < peers.Count; i++) { if (closestKnown is null || string.CompareOrdinal( Kademlia.CalculateDistance(peers[i].Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) < 1) { findNeighboursTasks.Add(FindPeerAsync( target, peers[i], (depth == -1) ? depth : depth + 1, timeout, cancellationToken)); } } try { await Task.WhenAll(findNeighboursTasks); } catch (TimeoutException) { if (findNeighboursTasks.All(findPeerTask => findPeerTask.IsFaulted)) { throw new TimeoutException( "Timeout exception occurred during ProcessFoundAsync()."); } } }
internal int GetBucketIndexOf(Address addr) { int plength = Kademlia.CommonPrefixLength(addr, _address); return(Math.Min(plength, TableSize - 1)); }
private int GetBucketIndexOf(Peer peer) { int plength = Kademlia.CommonPrefixLength(peer.Address, _address); return(Math.Min(plength, _tableSize - 1)); }
/// <summary> /// Process <see cref="Peer"/>s that is replied by sending <see cref="FindNeighbors"/> /// request. /// </summary> /// <param name="history"><see cref="Peer"/>s that already searched.</param> /// <param name="dialHistory"><see cref="Peer"/>s that ping sent.</param> /// <param name="found"><see cref="Peer"/>s that found.</param> /// <param name="target">The target <see cref="Address"/> to search.</param> /// <param name="depth">Target depth of recursive operation. If -1 is given, /// it runs until the closest peer is found.</param> /// <param name="timeout"><see cref="TimeSpan"/> for next depth's /// <see cref="FindPeerAsync"/> operation.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification /// that this operation should be canceled.</param> /// <returns>An awaitable task without value.</returns> /// <exception cref="TimeoutException">Thrown when all peers that found are /// not online.</exception> private async Task ProcessFoundAsync( ConcurrentBag <BoundPeer> history, ConcurrentBag <BoundPeer> dialHistory, IEnumerable <BoundPeer> found, Address target, int depth, TimeSpan?timeout, CancellationToken cancellationToken) { List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address) && !_table.Contains(peer) && !history.Contains(peer)).ToList(); if (peers.Count == 0) { _logger.Verbose("No any neighbor received."); return; } peers = Kademlia.SortByDistance(peers, target).ToList(); List <BoundPeer> closestCandidate = _table.Neighbors(target, _table.BucketSize, false).ToList(); Task[] awaitables = peers .Where(peer => !dialHistory.Contains(peer)) .Select( peer => { dialHistory.Add(peer); return(PingAsync(peer, _requestTimeout, cancellationToken)); } ).ToArray(); try { await Task.WhenAll(awaitables); } catch (Exception) { IEnumerable <AggregateException> exceptions = awaitables .Where(t => t.IsFaulted) .Select(t => t.Exception); foreach (var ae in exceptions) { var isTimeout = false; foreach (var ie in ae.InnerExceptions) { if (ie is PingTimeoutException pte) { peers.Remove(pte.Target); isTimeout = true; break; } } if (isTimeout) { break; } _logger.Warning( ae, "Some responses from neighbors found unexpectedly terminated: {ae}", ae ); } } var findPeerTasks = new List <Task>(); Peer closestKnown = closestCandidate.FirstOrDefault(); var count = 0; foreach (var peer in peers) { if (!(closestKnown is null) && string.CompareOrdinal( Kademlia.CalculateDistance(peer.Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) >= 1) { break; } if (history.Contains(peer)) { continue; } findPeerTasks.Add(FindPeerAsync( history, dialHistory, target, peer, depth == -1 ? depth : depth - 1, timeout, cancellationToken)); if (count++ >= _findConcurrency) { break; } } try { await Task.WhenAll(findPeerTasks); } catch (Exception e) { _logger.Error( e, "Some FindPeer tasks were unexpectedly terminated: {Exception}", e ); } }
private async Task <BoundPeer> ProcessFoundForSpecificAsync( ConcurrentBag <BoundPeer> history, IEnumerable <BoundPeer> found, Address target, int depth, TimeSpan?timeout, CancellationToken cancellationToken, Address searchAddress) { BoundPeer peerFound = null; List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address)).ToList(); if (peers.Count == 0) { _logger.Debug("No any neighbor received."); return(peerFound); } peers = Kademlia.SortByDistance(peers, target); List <BoundPeer> closestNeighbors = _routing.Neighbors(target, _bucketSize).ToList(); Task[] awaitables = peers.Select(peer => PingAsync(peer, _requestTimeout, cancellationToken) ).ToArray(); try { await Task.WhenAll(awaitables); } catch (AggregateException e) { if (e.InnerExceptions.All(ie => ie is TimeoutException) && e.InnerExceptions.Count == awaitables.Length) { throw new TimeoutException( $"All neighbors found do not respond in {_requestTimeout}." ); } _logger.Error( e, "Some responses from neighbors found unexpectedly terminated: {Exception}", e ); } for (int i = 0; i < closestNeighbors.Count; i++) { if (string.CompareOrdinal( closestNeighbors[i].Address.ToHex(), searchAddress.ToHex() ) == 0 && _routing.Contains(closestNeighbors[i])) { peerFound = closestNeighbors[i]; return(peerFound); } } var findNeighboursTasks = new List <Task <BoundPeer> >(); Peer closestKnown = closestNeighbors.Count == 0 ? null : closestNeighbors[0]; for (int i = 0; i < Kademlia.FindConcurrency && i < peers.Count; i++) { if (peerFound is null || string.CompareOrdinal( Kademlia.CalculateDistance(peers[i].Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) < 1) { findNeighboursTasks.Add(FindSpecificPeerAsync( history, target, peers[i], (depth == -1) ? depth : depth - 1, searchAddress, timeout, cancellationToken)); } } var foundSpecificPeer = new List <BoundPeer>(); try { foundSpecificPeer.AddRange(await Task.WhenAll(findNeighboursTasks)); } catch (TimeoutException) { if (findNeighboursTasks.All(findPeerTask => findPeerTask.IsFaulted)) { throw new TimeoutException( "Timeout exception occurred during " + $"{nameof(ProcessFoundForSpecificAsync)}."); } } for (int i = 0; i < foundSpecificPeer.Count; i++) { if (string.CompareOrdinal( foundSpecificPeer[i].Address.ToHex(), searchAddress.ToHex() ) == 0 && _routing.Contains(foundSpecificPeer[i])) { peerFound = foundSpecificPeer[i]; return(peerFound); } } return(peerFound); }
/// <summary> /// Process <see cref="Peer"/>s that is replied by sending <see cref="FindNeighbors"/> /// request. /// </summary> /// <param name="history"><see cref="Peer"/>s that already searched.</param> /// <param name="found"><see cref="Peer"/>s that found.</param> /// <param name="target">The target <see cref="Address"/> to search.</param> /// <param name="depth">Target depth of recursive operation. If -1 is given, /// it runs until the closest peer is found.</param> /// <param name="timeout"><see cref="TimeSpan"/> for next depth's /// <see cref="FindPeerAsync"/> operation.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification /// that this operation should be canceled.</param> /// <returns>An awaitable task without value.</returns> /// <exception cref="TimeoutException">Thrown when all peers that found are /// not online.</exception> private async Task ProcessFoundAsync( ConcurrentBag <BoundPeer> history, IEnumerable <BoundPeer> found, Address target, int depth, TimeSpan?timeout, CancellationToken cancellationToken) { List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address) && !_routing.Contains(peer) && !history.Contains(peer)).ToList(); if (peers.Count == 0) { _logger.Debug("No any neighbor received."); return; } peers = Kademlia.SortByDistance(peers, target); List <BoundPeer> closestCandidate = _routing.Neighbors(target, _bucketSize).ToList(); Task[] awaitables = peers.Select(peer => PingAsync(peer, _requestTimeout, cancellationToken) ).ToArray(); try { await Task.WhenAll(awaitables); } catch (Exception e) { _logger.Error( e, "Some responses from neighbors found unexpectedly terminated: {Exception}", e ); } var findPeerTasks = new List <Task>(); Peer closestKnown = closestCandidate.Count == 0 ? null : closestCandidate[0]; var count = 0; foreach (var peer in peers) { if (!(closestKnown is null) && string.CompareOrdinal( Kademlia.CalculateDistance(peer.Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) >= 1) { break; } if (history.Contains(peer)) { continue; } findPeerTasks.Add(FindPeerAsync( history, target, peer, depth == -1 ? depth : depth - 1, timeout, cancellationToken)); if (count++ >= Kademlia.FindConcurrency) { break; } } try { await Task.WhenAll(findPeerTasks); } catch (Exception e) { _logger.Error( e, "Some FindPeer tasks were unexpectedly terminated: {Exception}", e ); } }
private async Task ProcessFoundAsync( ImmutableList <BoundPeer> found, Address target, TimeSpan?timeout, CancellationToken cancellationToken) { List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address) && !_routing.Contains(peer)).ToList(); if (peers.Count == 0) { _logger.Debug("No any neighbor received."); return; } peers = Kademlia.SortByDistance(peers, target); List <BoundPeer> closestCandidate = _routing.Neighbors(target, BucketSize).ToList(); bool foundPingTimeout = true; foreach (BoundPeer peer in peers) { try { // This timeout should be request timeout? await PingAsync(peer, RequestTimeout, cancellationToken); foundPingTimeout = false; } catch (TimeoutException) { continue; } } if (foundPingTimeout) { _logger.Debug("All neighbors found are invalid."); throw new TimeoutException(); } var findNeighboursTasks = new List <Task>(); Peer closestKnown = closestCandidate.Count == 0 ? null : closestCandidate[0]; for (int i = 0; i < FindConcurrency && i < peers.Count; i++) { if (closestKnown is null || string.CompareOrdinal( Kademlia.CalculateDistance(peers[i].Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) < 1) { findNeighboursTasks.Add( FindPeerAsync(target, peers[i], timeout, cancellationToken)); } } try { await Task.WhenAll(findNeighboursTasks); } catch (TimeoutException) { if (findNeighboursTasks.All(findPeerTask => findPeerTask.IsFaulted)) { throw new TimeoutException( "Timeout exception occurred during ProcessFoundAsync()."); } } }