< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Transport.Services.TcpService
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Infrastructure/Transport/Services/TcpService.cs
Tag: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 86
Coverable lines: 86
Total lines: 173
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 24
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
get_ListeningTo()100%210%
StartListeningAsync(...)0%7280%
StopListeningAsync()0%4260%
ConnectToPeerAsync()100%210%
ListenForConnectionsAsync()0%4260%

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Infrastructure/Transport/Services/TcpService.cs

#LineLine coverage
 1using System.Net;
 2using System.Net.Sockets;
 3using Microsoft.Extensions.Logging;
 4using Microsoft.Extensions.Options;
 5
 6namespace NLightning.Infrastructure.Transport.Services;
 7
 8using Domain.Exceptions;
 9using Domain.Node.Options;
 10using Events;
 11using Interfaces;
 12using Node.ValueObjects;
 13using Protocol.Models;
 14
 15public class TcpService : ITcpService
 16{
 17    private readonly ILogger<TcpService> _logger;
 18    private readonly NodeOptions _nodeOptions;
 019    private readonly List<TcpListener> _listeners = [];
 20
 21    private CancellationTokenSource? _cts;
 22    private Task? _listeningTask;
 23
 024    public List<EndPoint> ListeningTo => _listeners.Select(l => l.LocalEndpoint).ToList();
 25
 26    /// <inheritdoc />
 27    public event EventHandler<NewPeerConnectedEventArgs>? OnNewPeerConnected;
 28
 029    public TcpService(ILogger<TcpService> logger, IOptions<NodeOptions> nodeOptions)
 30    {
 031        _logger = logger;
 032        _nodeOptions = nodeOptions.Value;
 033    }
 34
 35    /// <inheritdoc />
 36    public Task StartListeningAsync(CancellationToken cancellationToken)
 37    {
 038        _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 39
 040        foreach (var address in _nodeOptions.ListenAddresses)
 41        {
 042            var parts = address.Split(':');
 043            if (parts.Length != 2 || !int.TryParse(parts[1], out var port))
 44            {
 045                _logger.LogWarning("Invalid listen address: {Address}", address);
 046                continue;
 47            }
 48
 049            var ipAddress = IPAddress.Parse(parts[0]);
 050            var listener = new TcpListener(ipAddress, port);
 051            listener.Start();
 052            _listeners.Add(listener);
 53
 054            if (_logger.IsEnabled(LogLevel.Information))
 055                _logger.LogInformation("Listening for connections on {Address}:{Port}", ipAddress, port);
 56        }
 57
 058        _listeningTask = ListenForConnectionsAsync(_cts.Token);
 59
 060        return Task.CompletedTask;
 61    }
 62
 63    /// <inheritdoc />
 64    public async Task StopListeningAsync()
 65    {
 066        if (_cts is null)
 067            throw new InvalidOperationException("Service is not running");
 68
 069        await _cts.CancelAsync();
 70
 071        foreach (var listener in _listeners)
 72        {
 73            try
 74            {
 075                listener.Stop();
 076            }
 077            catch (Exception ex)
 78            {
 079                _logger.LogError(ex, "Error stopping listener");
 080            }
 81        }
 82
 083        _listeners.Clear();
 84
 085        if (_listeningTask is not null)
 86        {
 87            try
 88            {
 089                await _listeningTask;
 090            }
 091            catch (OperationCanceledException)
 92            {
 93                // Expected during cancellation
 094            }
 95        }
 096    }
 97
 98    /// <inheritdoc />
 99    /// <exception cref="ConnectionException">Thrown when the connection to the peer fails.</exception>
 100    public async Task<ConnectedPeer> ConnectToPeerAsync(PeerAddress peerAddress)
 101    {
 0102        var tcpClient = new TcpClient();
 103        try
 104        {
 0105            await tcpClient.ConnectAsync(peerAddress.Host, peerAddress.Port,
 0106                                         new CancellationTokenSource(_nodeOptions.NetworkTimeout).Token);
 107
 0108            return new ConnectedPeer(peerAddress.PubKey, peerAddress.Host.ToString(), (uint)peerAddress.Port,
 0109                                     tcpClient);
 110        }
 0111        catch (OperationCanceledException)
 112        {
 0113            throw new ConnectionException($"Timeout connecting to peer {peerAddress.Host}:{peerAddress.Port}");
 114        }
 0115        catch (Exception e)
 116        {
 0117            throw new ConnectionException($"Failed to connect to peer {peerAddress.Host}:{peerAddress.Port}", e);
 118        }
 0119    }
 120
 121    private async Task ListenForConnectionsAsync(CancellationToken cancellationToken)
 122    {
 123        try
 124        {
 0125            while (!cancellationToken.IsCancellationRequested)
 126            {
 0127                foreach (var listener in _listeners)
 128                {
 0129                    if (!listener.Pending())
 130                        continue;
 131
 0132                    var tcpClient = await listener.AcceptTcpClientAsync(cancellationToken);
 0133                    _ = Task.Run(() =>
 0134                    {
 0135                        try
 0136                        {
 0137                            _logger.LogInformation("New peer connection from {RemoteEndPoint}",
 0138                                                   tcpClient.Client.RemoteEndPoint);
 0139
 0140                            if (tcpClient.Client.RemoteEndPoint is not IPEndPoint ipEndPoint)
 0141                            {
 0142                                _logger.LogError("Failed to get remote endpoint for {RemoteEndPoint}",
 0143                                                 tcpClient.Client.RemoteEndPoint);
 0144                                return;
 0145                            }
 0146
 0147                            // Raise the event for a new peer connection
 0148                            OnNewPeerConnected?.Invoke(
 0149                                this,
 0150                                new NewPeerConnectedEventArgs(ipEndPoint.Address.ToString(), (uint)ipEndPoint.Port,
 0151                                                              tcpClient));
 0152                        }
 0153                        catch (Exception e)
 0154                        {
 0155                            _logger.LogError(e, "Error accepting peer connection for {RemoteEndPoint}",
 0156                                             tcpClient.Client.RemoteEndPoint);
 0157                        }
 0158                    }, cancellationToken);
 0159                }
 160
 0161                await Task.Delay(100, cancellationToken); // Avoid busy-waiting
 162            }
 0163        }
 0164        catch (OperationCanceledException)
 165        {
 0166            _logger.LogInformation("Stopping listener service");
 0167        }
 0168        catch (Exception e)
 169        {
 0170            _logger.LogError(e, "Unhandled exception in listener service");
 0171        }
 0172    }
 173}