< Summary - Combined Code Coverage

Information
Class: NLightning.Daemon.Services.Ipc.NamedPipeIpcService
Assembly: NLightning.Daemon
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Daemon/Services/Ipc/NamedPipeIpcService.cs
Tag: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 73
Coverable lines: 73
Total lines: 165
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 16
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%
StartAsync(...)100%210%
StopAsync()0%2040%
ListenToIpcClientAsync()0%620%
HandleClientAsync()0%2040%
EnsureCookieExists()0%4260%

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Daemon/Services/Ipc/NamedPipeIpcService.cs

#LineLine coverage
 1using System.IO.Pipes;
 2using Microsoft.Extensions.Logging;
 3
 4namespace NLightning.Daemon.Services.Ipc;
 5
 6using Contracts.Utilities;
 7using Daemon.Ipc.Interfaces;
 8using Domain.Client.Constants;
 9using Domain.Client.Interfaces;
 10using Factories;
 11using Transport.Ipc;
 12
 13/// <summary>
 14/// Hosted service that listens to on a named pipe and processes IPC requests using injected components.
 15/// </summary>
 16internal sealed class NamedPipeIpcService : INamedPipeIpcService
 17{
 18    private readonly ILogger<NamedPipeIpcService> _logger;
 19    private readonly IIpcAuthenticator _authenticator;
 20    private readonly IIpcFraming _framing;
 21    private readonly IIpcRequestRouter _router;
 22    private readonly string _pipeName;
 23    private readonly string _cookiePath;
 24
 25    private CancellationTokenSource? _cts;
 26    private Task? _listenerTask;
 27
 028    public NamedPipeIpcService(IIpcAuthenticator authenticator, string configPath, IIpcFraming framing,
 029                               ILogger<NamedPipeIpcService> logger, IIpcRequestRouter router)
 30    {
 031        _logger = logger;
 032        _authenticator = authenticator;
 033        _framing = framing;
 034        _router = router;
 35
 036        _pipeName = NodeUtils.GetNamedPipeFilePath(configPath);
 037        _cookiePath = NodeUtils.GetCookieFilePath(configPath);
 038    }
 39
 40    public Task StartAsync(CancellationToken cancellationToken)
 41    {
 042        _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 43
 044        EnsureCookieExists();
 45
 046        _listenerTask = ListenToIpcClientAsync(cancellationToken);
 47
 048        return Task.CompletedTask;
 49    }
 50
 51    public async Task StopAsync()
 52    {
 053        if (_cts is null)
 054            throw new InvalidOperationException("Service is not running");
 55
 056        await _cts.CancelAsync();
 57
 058        if (_listenerTask is not null)
 59        {
 60            try
 61            {
 062                await _listenerTask;
 063            }
 064            catch (OperationCanceledException)
 65            {
 66                // Expected during cancellation
 067            }
 68        }
 069    }
 70
 71    private async Task ListenToIpcClientAsync(CancellationToken cancellationToken)
 72    {
 73        try
 74        {
 075            while (!cancellationToken.IsCancellationRequested)
 76            {
 77                try
 78                {
 079                    var server = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, 10,
 080                                                           PipeTransmissionMode.Byte,
 081                                                           PipeOptions.Asynchronous);
 082                    await server.WaitForConnectionAsync(cancellationToken);
 83
 084                    _ = Task.Run(() => HandleClientAsync(server, cancellationToken), cancellationToken);
 085                }
 086                catch (Exception ex) when (!cancellationToken.IsCancellationRequested)
 87                {
 088                    _logger.LogError(ex, "IPC server accept loop error");
 089                    await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
 90                }
 91            }
 092        }
 093        catch (OperationCanceledException)
 94        {
 095            _logger.LogInformation("IPC server loop cancelled");
 096        }
 097        catch (Exception ex)
 98        {
 099            _logger.LogError(ex, "Fatal error in IPC server loop");
 0100        }
 0101    }
 102
 103    private async Task HandleClientAsync(NamedPipeServerStream stream, CancellationToken ct)
 104    {
 105        try
 106        {
 0107            var request = await _framing.ReadAsync(stream, ct);
 108
 0109            if (!await _authenticator.ValidateAsync(request.AuthToken, ct))
 110            {
 0111                var err = IpcErrorFactory.CreateErrorEnvelope(request, ErrorCodes.AuthenticationFailure,
 0112                                                              "Authentication failed.");
 0113                await _framing.WriteAsync(stream, err, ct);
 0114                return;
 115            }
 116
 0117            var response = await _router.RouteAsync(request, ct);
 0118            await _framing.WriteAsync(stream, response, ct);
 0119        }
 0120        catch (Exception ex)
 121        {
 0122            _logger.LogError(ex, "IPC client handling failed");
 123            try
 124            {
 125                // Try to write a generic error if we still can read an envelope
 0126                var env = new IpcEnvelope { Version = 1, CorrelationId = Guid.NewGuid(), Kind = IpcEnvelopeKind.Error };
 0127                var err = IpcErrorFactory.CreateErrorEnvelope(env, ErrorCodes.ServerError, ex.Message);
 0128                await _framing.WriteAsync(stream, err, ct);
 0129            }
 0130            catch
 131            {
 132                // ignore
 0133            }
 134        }
 135        finally
 136        {
 0137            try { await stream.DisposeAsync(); }
 0138            catch
 139            {
 140                //ignore
 0141            }
 142        }
 0143    }
 144
 145    private void EnsureCookieExists()
 146    {
 147        try
 148        {
 0149            var dir = Path.GetDirectoryName(_cookiePath);
 0150            if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
 0151                Directory.CreateDirectory(dir);
 152
 0153            if (File.Exists(_cookiePath))
 0154                return;
 155
 0156            var token = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
 0157            File.WriteAllText(_cookiePath, token);
 0158        }
 0159        catch (Exception ex)
 160        {
 0161            _logger.LogError(ex, "Failed to ensure IPC cookie exists at {Path}", _cookiePath);
 0162            throw;
 163        }
 0164    }
 165}