< 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: 36_15743069263
Line coverage
0%
Covered lines: 0
Uncovered lines: 85
Coverable lines: 85
Total lines: 173
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
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%
StartListeningAsync(...)0%4260%
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;
 5using NLightning.Domain.Exceptions;
 6using NLightning.Domain.Node.ValueObjects;
 7using NLightning.Infrastructure.Node.ValueObjects;
 8using NLightning.Infrastructure.Protocol.Models;
 9
 10namespace NLightning.Infrastructure.Transport.Services;
 11
 12using Domain.Node.Options;
 13using Events;
 14using Interfaces;
 15
 16public class TcpService : ITcpService
 17{
 18    private readonly ILogger<TcpService> _logger;
 19    private readonly NodeOptions _nodeOptions;
 020    private readonly List<TcpListener> _listeners = [];
 21
 22    private CancellationTokenSource? _cts;
 23    private Task? _listeningTask;
 24
 25    /// <inheritdoc />
 26    public event EventHandler<NewPeerConnectedEventArgs>? OnNewPeerConnected;
 27
 028    public TcpService(ILogger<TcpService> logger, IOptions<NodeOptions> nodeOptions)
 29    {
 030        _logger = logger;
 031        _nodeOptions = nodeOptions.Value;
 032    }
 33
 34    /// <inheritdoc />
 35    public Task StartListeningAsync(CancellationToken cancellationToken)
 36    {
 037        _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 38
 039        foreach (var address in _nodeOptions.ListenAddresses)
 40        {
 041            var parts = address.Split(':');
 042            if (parts.Length != 2 || !int.TryParse(parts[1], out var port))
 43            {
 044                _logger.LogWarning("Invalid listen address: {Address}", address);
 045                continue;
 46            }
 47
 048            var ipAddress = IPAddress.Parse(parts[0]);
 049            var listener = new TcpListener(ipAddress, port);
 050            listener.Start();
 051            _listeners.Add(listener);
 52
 053            _logger.LogInformation("Listening for connections on {Address}:{Port}", ipAddress, port);
 54        }
 55
 056        _listeningTask = ListenForConnectionsAsync(_cts.Token);
 57
 058        return Task.CompletedTask;
 59    }
 60
 61    /// <inheritdoc />
 62    public async Task StopListeningAsync()
 63    {
 064        if (_cts is null)
 065            throw new InvalidOperationException("Service is not running");
 66
 067        await _cts.CancelAsync();
 68
 069        foreach (var listener in _listeners)
 70        {
 71            try
 72            {
 073                listener.Stop();
 074            }
 075            catch (Exception ex)
 76            {
 077                _logger.LogError(ex, "Error stopping listener");
 078            }
 79        }
 80
 081        _listeners.Clear();
 82
 083        if (_listeningTask is not null)
 84        {
 85            try
 86            {
 087                await _listeningTask;
 088            }
 089            catch (OperationCanceledException)
 90            {
 91                // Expected during cancellation
 092            }
 93        }
 094    }
 95
 96    /// <inheritdoc />
 97    /// <exception cref="ConnectionException">Thrown when the connection to the peer fails.</exception>
 98    public async Task<ConnectedPeer> ConnectToPeerAsync(PeerAddressInfo peerAddressInfo)
 99    {
 0100        var peerAddress = new PeerAddress(peerAddressInfo.Address);
 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}