diff --git a/Services/IPCServerService.cs b/Services/IPCService.cs similarity index 73% rename from Services/IPCServerService.cs rename to Services/IPCService.cs index 4a46ed9..3a12602 100644 --- a/Services/IPCServerService.cs +++ b/Services/IPCService.cs @@ -1,57 +1,44 @@ /* This software is licensed by the MIT License, see LICENSE file */ /* Copyright © 2024-2025 Gregory Lirent */ -#if WINIPC_UA_SERVER +#if !WINIPC_UA_SERVER +using Google.Protobuf; +#else using Microsoft.Extensions.Options; using System.Diagnostics; -using System.IO.Pipes; using WinIPC.Config; +#endif + +using System.IO.Pipes; using WinIPC.Models; using WinIPC.Utils; namespace WinIPC.Services { - public class IPCServerService : IHostedService, IDisposable + public class IPCService : IHostedService, IDisposable { - private readonly ILogger _logger; - private readonly IPCServiceOptions _options; - private readonly string _pipeName; + protected readonly ILogger _logger; + protected string _name; + protected Task? _task; + protected CancellationTokenSource? _cTok; + protected PipeStream? _pipe; - private NamedPipeServerStream? _pipe; - private CancellationTokenSource? _cTok; - private Task? _task; - - private Func>? _handler; - - public IPCServerService(ILogger logger, IOptions appConfigOptions) + protected string GetPipeName(int sessionId = 0) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _options = appConfigOptions?.Value?.IPCService ?? throw new ArgumentNullException(nameof(appConfigOptions)); + if (sessionId == 0) + return _name; - int sid = Process.GetCurrentProcess().SessionId; - if (sid == 0) - { - _logger.LogWarning("Server: Failed to get current Session ID (returned 0). Using base pipe name without Session ID suffix."); - _pipeName = _options.BasePipeName; - } - else - { - _pipeName = $"{_options.BasePipeName}.{sid}"; - _logger.LogInformation("Server: Appending Session ID {SessionId} to pipe name. Actual pipe name: {ActualPipeName}", sid, _pipeName); - } - - _logger.LogInformation("IPCServerService initialized. Actual pipe name: {ActualPipeName}", _pipeName); + return $"{_name}.{sessionId}"; } - - public Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cTok) { - _logger.LogInformation("IPCServerService is starting..."); + _logger.LogInformation("IPCService is starting..."); return Task.CompletedTask; } - public async Task StopAsync(CancellationToken cancellationToken) + public async Task StopAsync(CancellationToken cTok) { - _logger.LogInformation("IPCServerService is stopping..."); + _logger.LogInformation("IPCService is stopping..."); _cTok?.Cancel(); if (_task != null) @@ -65,7 +52,7 @@ namespace WinIPC.Services } Dispose(); - _logger.LogInformation("IPCServerService stopped."); + _logger.LogInformation("IPCService stopped."); } public void Dispose() @@ -75,33 +62,98 @@ namespace WinIPC.Services GC.SuppressFinalize(this); } + protected IPCService(ILogger logger, string name = "") + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _name = name; + } + + protected async Task ReadMessageAsync(PipeStream pipe, CancellationToken cTok) + { + byte[] lengthBuffer = new byte[4]; + int bytesRead; + + bytesRead = await pipe.ReadAsync(lengthBuffer, 0, 4, cTok); + if (bytesRead == 0) throw new EndOfStreamException("Pipe closed or no data for length prefix."); + if (bytesRead < 4) throw new IOException($"Failed to read full 4-byte length prefix. Read {bytesRead} bytes."); + + int messageLength = BitConverter.ToInt32(lengthBuffer, 0); + if (messageLength <= 0) throw new InvalidDataException($"Received invalid message length: {messageLength}."); + + byte[] messageBuffer = new byte[messageLength]; + int totalBytesRead = 0; + while (totalBytesRead < messageLength) + { + bytesRead = await pipe.ReadAsync(messageBuffer, totalBytesRead, messageLength - totalBytesRead, cTok); + if (bytesRead == 0) throw new EndOfStreamException($"Pipe closed unexpectedly while reading message payload. Expected {messageLength} bytes, read {totalBytesRead}."); + totalBytesRead += bytesRead; + } + + return messageBuffer; + } + + protected async Task WriteMessageAsync(PipeStream pipe, byte[] buffer, CancellationToken cTok) + { + try + { + byte[] lengthPrefix = BitConverter.GetBytes(buffer.Length); + await pipe.WriteAsync(lengthPrefix, 0, lengthPrefix.Length, cTok); + await pipe.WriteAsync(buffer, 0, buffer.Length, cTok); + await pipe.FlushAsync(cTok); + return true; + } + catch (OperationCanceledException) + { + _logger.LogInformation("WriteMessageAsync: Operation cancelled."); + } + catch (Exception ex) + { + _logger.LogError(ex, $"WriteMessageAsync: Error writing to pipe. Buffer size: {buffer.Length} bytes."); + } + return false; + } + } + + +#if WINIPC_UA_SERVER + public sealed class IPCServerService : IPCService + { + private Func>? _handler; + private string _pipeName = String.Empty; + + public IPCServerService(ILogger logger, IOptions appConfigOptions) + :base(logger, appConfigOptions?.Value?.IPCService.BasePipeName ?? throw new ArgumentNullException(nameof(appConfigOptions))) + { } + public Task StartListening(Func> handler, CancellationToken cTok) { _handler = handler ?? throw new ArgumentNullException(nameof(handler)); _cTok = CancellationTokenSource.CreateLinkedTokenSource(cTok); - _task = Task.Run(() => ServerListenLoop(_cTok.Token), _cTok.Token); + _task = Task.Run(() => Listen(_cTok.Token), _cTok.Token); return _task; } - private async Task ServerListenLoop(CancellationToken cTok) + private async Task Listen(CancellationToken cTok) { + _pipeName = GetPipeName(Process.GetCurrentProcess().SessionId); _logger.LogInformation($"Server: Starting listen loop on pipe '{_pipeName}'..."); while (!cTok.IsCancellationRequested) { try { - _pipe = new NamedPipeServerStream( + var pipe = new NamedPipeServerStream( _pipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + _pipe = pipe; _logger.LogInformation("Server: Waiting for client connection..."); - await _pipe.WaitForConnectionAsync(cTok); + await pipe.WaitForConnectionAsync(cTok); _logger.LogInformation("Server: Client connected."); - _ = HandleClientConnectionAsync(_pipe, cTok); + _ = HandleClientConnectionAsync(pipe, cTok); } catch (OperationCanceledException) { @@ -218,52 +270,56 @@ namespace WinIPC.Services return PayloadHandler.CreateErrorResponse(request.Id, "Invalid request format."); } } + } +#else + public abstract class IPCBaseClientService : IPCService + { + private uint _id = 0; + private Response? _resp; - private async Task ReadMessageAsync(PipeStream pipe, CancellationToken cTok) + protected IPCBaseClientService(ILogger logger, string name) + : base(logger, name) { } + + public async Task GetResponseAsync() { - byte[] lengthBuffer = new byte[4]; - int bytesRead; + if (_task == null) + return null; - bytesRead = await pipe.ReadAsync(lengthBuffer, 0, 4, cTok); - if (bytesRead == 0) throw new EndOfStreamException("Pipe closed or no data for length prefix."); - if (bytesRead < 4) throw new IOException($"Failed to read full 4-byte length prefix. Read {bytesRead} bytes."); + await _task; - int messageLength = BitConverter.ToInt32(lengthBuffer, 0); - if (messageLength <= 0) throw new InvalidDataException($"Received invalid message length: {messageLength}."); + _pipe?.Dispose(); + _pipe = null; + _task = null; - byte[] messageBuffer = new byte[messageLength]; - int totalBytesRead = 0; - while (totalBytesRead < messageLength) - { - bytesRead = await pipe.ReadAsync(messageBuffer, totalBytesRead, messageLength - totalBytesRead, cTok); - if (bytesRead == 0) throw new EndOfStreamException($"Pipe closed unexpectedly while reading message payload. Expected {messageLength} bytes, read {totalBytesRead}."); - totalBytesRead += bytesRead; - } - - return messageBuffer; + return _resp; } - private async Task WriteMessageAsync(PipeStream pipe, byte[] buffer, CancellationToken cTok) + private async Task SendRequestAsync(NamedPipeClientStream pipe, Request request) { - try + _pipe = pipe; + await pipe.ConnectAsync(5000); + Console.WriteLine("Connected to pipe!"); + + if (await WriteMessageAsync(pipe, PayloadHandler.Serialize(request), _cTok?.Token ?? CancellationToken.None)) { - byte[] lengthPrefix = BitConverter.GetBytes(buffer.Length); - await pipe.WriteAsync(lengthPrefix, 0, lengthPrefix.Length, cTok); - await pipe.WriteAsync(buffer, 0, buffer.Length, cTok); - await pipe.FlushAsync(cTok); - return true; + _resp = PayloadHandler.Deserialize(await ReadMessageAsync(pipe, _cTok?.Token ?? CancellationToken.None)); } - catch (OperationCanceledException) + else { - _logger.LogInformation("WriteMessageAsync: Operation cancelled."); - return false; + throw new IOException("Failed to write request into named pipe"); } - catch (Exception ex) - { - _logger.LogError(ex, $"WriteMessageAsync: Error writing to pipe. Buffer size: {buffer.Length} bytes."); - } - return false; + } + + public IPCBaseClientService SendRequest(int sessionId, Request request) + { + _task = SendRequestAsync(new NamedPipeClientStream(".", GetPipeName(sessionId), PipeDirection.InOut), request); + return this; + } + + public IPCBaseClientService SendRequest(int sessionId, CommandType type, T data) where T : IMessage + { + return SendRequest(sessionId, PayloadHandler.CreateRequest(++_id, type, data)); } } +#endif } -#endif \ No newline at end of file diff --git a/Services/UserAgentService.cs b/Services/UserAgentService.cs index c67a1e6..043550f 100644 --- a/Services/UserAgentService.cs +++ b/Services/UserAgentService.cs @@ -113,24 +113,14 @@ namespace WinIPC.Services { try { - var data = new List(); + var resp = new WindowsResponse(); foreach (var info in await _windowScanner.ScanAsync()) { - if (req.Hwnds.Count == 0 || req.Hwnds.Contains(info.Hwnd)) data.Add(info); + if (req.Hwnds.Count == 0 || req.Hwnds.Contains(info.Hwnd)) resp.Data.Add(info); } - var windowsResponse = new WindowsResponse - { - Data = { data } - }; - - return new Response - { - Id = requestId, - Success = true, - Payload = ByteString.CopyFrom(PayloadHandler.Serialize(windowsResponse)) - }; + return PayloadHandler.CreateResponse(requestId, true, null, resp); } catch (Exception ex) { @@ -219,16 +209,12 @@ namespace WinIPC.Services using (MemoryStream ms = new MemoryStream()) { screenshot.Save(ms, ImageFormat.Png); - return new Response + + return PayloadHandler.CreateResponse(requestId, true, null, new ScreenshotResponse { - Id = requestId, - Success = true, - Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new ScreenshotResponse - { - Size = new AreaSize { Width = screenshot.Width, Height = screenshot.Height }, - Data = ByteString.CopyFrom(ms.ToArray()) - })) - }; + Size = new AreaSize { Width = screenshot.Width, Height = screenshot.Height }, + Data = ByteString.CopyFrom(ms.ToArray()) + }); } } } @@ -262,15 +248,10 @@ namespace WinIPC.Services Color color = screenPixel.GetPixel(0, 0); - return new Response + return PayloadHandler.CreateResponse(requestId, true, null, new PixelResponse { - Id = requestId, - Success = true, - Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new PixelResponse - { - RgbColor = (uint)((color.R << 16) | (color.G << 8) | color.B) - })) - }; + RgbColor = (uint)((color.R << 16) | (color.G << 8) | color.B) + }); } } catch (Exception ex) @@ -330,15 +311,7 @@ namespace WinIPC.Services } } - return new Response - { - Id = requestId, - Success = true, - Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new InputResponse - { - Count = nSuccess - })) - }; + return PayloadHandler.CreateResponse(requestId, true, null, new InputResponse { Count = nSuccess }); } public void Dispose()