Update IPCService

This commit is contained in:
Gregory Lirent 2025-08-03 13:58:46 +03:00
parent 4a8b51715c
commit 838ab6852f
2 changed files with 141 additions and 112 deletions

View File

@ -1,57 +1,44 @@
/* This software is licensed by the MIT License, see LICENSE file */ /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */ /* Copyright © 2024-2025 Gregory Lirent */
#if WINIPC_UA_SERVER #if !WINIPC_UA_SERVER
using Google.Protobuf;
#else
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Diagnostics; using System.Diagnostics;
using System.IO.Pipes;
using WinIPC.Config; using WinIPC.Config;
#endif
using System.IO.Pipes;
using WinIPC.Models; using WinIPC.Models;
using WinIPC.Utils; using WinIPC.Utils;
namespace WinIPC.Services namespace WinIPC.Services
{ {
public class IPCServerService : IHostedService, IDisposable public class IPCService : IHostedService, IDisposable
{ {
private readonly ILogger<IPCServerService> _logger; protected readonly ILogger _logger;
private readonly IPCServiceOptions _options; protected string _name;
private readonly string _pipeName; protected Task? _task;
protected CancellationTokenSource? _cTok;
protected PipeStream? _pipe;
private NamedPipeServerStream? _pipe; protected string GetPipeName(int sessionId = 0)
private CancellationTokenSource? _cTok;
private Task? _task;
private Func<Request, Task<Response>>? _handler;
public IPCServerService(ILogger<IPCServerService> logger, IOptions<AppConfig> appConfigOptions)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (sessionId == 0)
_options = appConfigOptions?.Value?.IPCService ?? throw new ArgumentNullException(nameof(appConfigOptions)); return _name;
int sid = Process.GetCurrentProcess().SessionId; return $"{_name}.{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);
} }
public Task StartAsync(CancellationToken cTok)
public Task StartAsync(CancellationToken cancellationToken)
{ {
_logger.LogInformation("IPCServerService is starting..."); _logger.LogInformation("IPCService is starting...");
return Task.CompletedTask; 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(); _cTok?.Cancel();
if (_task != null) if (_task != null)
@ -65,7 +52,7 @@ namespace WinIPC.Services
} }
Dispose(); Dispose();
_logger.LogInformation("IPCServerService stopped."); _logger.LogInformation("IPCService stopped.");
} }
public void Dispose() public void Dispose()
@ -75,33 +62,98 @@ namespace WinIPC.Services
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
protected IPCService(ILogger logger, string name = "")
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_name = name;
}
protected async Task<byte[]> 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<bool> 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<Request, Task<Response>>? _handler;
private string _pipeName = String.Empty;
public IPCServerService(ILogger<IPCServerService> logger, IOptions<AppConfig> appConfigOptions)
:base(logger, appConfigOptions?.Value?.IPCService.BasePipeName ?? throw new ArgumentNullException(nameof(appConfigOptions)))
{ }
public Task StartListening(Func<Request, Task<Response>> handler, CancellationToken cTok) public Task StartListening(Func<Request, Task<Response>> handler, CancellationToken cTok)
{ {
_handler = handler ?? throw new ArgumentNullException(nameof(handler)); _handler = handler ?? throw new ArgumentNullException(nameof(handler));
_cTok = CancellationTokenSource.CreateLinkedTokenSource(cTok); _cTok = CancellationTokenSource.CreateLinkedTokenSource(cTok);
_task = Task.Run(() => ServerListenLoop(_cTok.Token), _cTok.Token); _task = Task.Run(() => Listen(_cTok.Token), _cTok.Token);
return _task; 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}'..."); _logger.LogInformation($"Server: Starting listen loop on pipe '{_pipeName}'...");
while (!cTok.IsCancellationRequested) while (!cTok.IsCancellationRequested)
{ {
try try
{ {
_pipe = new NamedPipeServerStream( var pipe = new NamedPipeServerStream(
_pipeName, _pipeName,
PipeDirection.InOut, PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances, NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte, PipeTransmissionMode.Byte,
PipeOptions.Asynchronous); PipeOptions.Asynchronous);
_pipe = pipe;
_logger.LogInformation("Server: Waiting for client connection..."); _logger.LogInformation("Server: Waiting for client connection...");
await _pipe.WaitForConnectionAsync(cTok); await pipe.WaitForConnectionAsync(cTok);
_logger.LogInformation("Server: Client connected."); _logger.LogInformation("Server: Client connected.");
_ = HandleClientConnectionAsync(_pipe, cTok); _ = HandleClientConnectionAsync(pipe, cTok);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -218,52 +270,56 @@ namespace WinIPC.Services
return PayloadHandler.CreateErrorResponse(request.Id, "Invalid request format."); return PayloadHandler.CreateErrorResponse(request.Id, "Invalid request format.");
} }
} }
}
#else
public abstract class IPCBaseClientService : IPCService
{
private uint _id = 0;
private Response? _resp;
private async Task<byte[]> ReadMessageAsync(PipeStream pipe, CancellationToken cTok) protected IPCBaseClientService(ILogger logger, string name)
: base(logger, name) { }
public async Task<Response?> GetResponseAsync()
{ {
byte[] lengthBuffer = new byte[4]; if (_task == null)
int bytesRead; return null;
bytesRead = await pipe.ReadAsync(lengthBuffer, 0, 4, cTok); await _task;
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); _pipe?.Dispose();
if (messageLength <= 0) throw new InvalidDataException($"Received invalid message length: {messageLength}."); _pipe = null;
_task = null;
byte[] messageBuffer = new byte[messageLength]; return _resp;
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;
} }
private async Task<bool> 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); _resp = PayloadHandler.Deserialize<Response>(await ReadMessageAsync(pipe, _cTok?.Token ?? CancellationToken.None));
await pipe.WriteAsync(lengthPrefix, 0, lengthPrefix.Length, cTok);
await pipe.WriteAsync(buffer, 0, buffer.Length, cTok);
await pipe.FlushAsync(cTok);
return true;
} }
catch (OperationCanceledException) else
{ {
_logger.LogInformation("WriteMessageAsync: Operation cancelled."); throw new IOException("Failed to write request into named pipe");
return false;
} }
catch (Exception ex) }
{
_logger.LogError(ex, $"WriteMessageAsync: Error writing to pipe. Buffer size: {buffer.Length} bytes."); public IPCBaseClientService SendRequest(int sessionId, Request request)
} {
return false; _task = SendRequestAsync(new NamedPipeClientStream(".", GetPipeName(sessionId), PipeDirection.InOut), request);
return this;
}
public IPCBaseClientService SendRequest<T>(int sessionId, CommandType type, T data) where T : IMessage<T>
{
return SendRequest(sessionId, PayloadHandler.CreateRequest(++_id, type, data));
} }
} }
#endif
} }
#endif

View File

@ -113,24 +113,14 @@ namespace WinIPC.Services
{ {
try try
{ {
var data = new List<WindowInfo>(); var resp = new WindowsResponse();
foreach (var info in await _windowScanner.ScanAsync()) 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 return PayloadHandler.CreateResponse(requestId, true, null, resp);
{
Data = { data }
};
return new Response
{
Id = requestId,
Success = true,
Payload = ByteString.CopyFrom(PayloadHandler.Serialize(windowsResponse))
};
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -219,16 +209,12 @@ namespace WinIPC.Services
using (MemoryStream ms = new MemoryStream()) using (MemoryStream ms = new MemoryStream())
{ {
screenshot.Save(ms, ImageFormat.Png); screenshot.Save(ms, ImageFormat.Png);
return new Response
return PayloadHandler.CreateResponse(requestId, true, null, new ScreenshotResponse
{ {
Id = requestId, Size = new AreaSize { Width = screenshot.Width, Height = screenshot.Height },
Success = true, Data = ByteString.CopyFrom(ms.ToArray())
Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new ScreenshotResponse });
{
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); Color color = screenPixel.GetPixel(0, 0);
return new Response return PayloadHandler.CreateResponse(requestId, true, null, new PixelResponse
{ {
Id = requestId, RgbColor = (uint)((color.R << 16) | (color.G << 8) | color.B)
Success = true, });
Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new PixelResponse
{
RgbColor = (uint)((color.R << 16) | (color.G << 8) | color.B)
}))
};
} }
} }
catch (Exception ex) catch (Exception ex)
@ -330,15 +311,7 @@ namespace WinIPC.Services
} }
} }
return new Response return PayloadHandler.CreateResponse(requestId, true, null, new InputResponse { Count = nSuccess });
{
Id = requestId,
Success = true,
Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new InputResponse
{
Count = nSuccess
}))
};
} }
public void Dispose() public void Dispose()