Update IPCService
This commit is contained in:
parent
4a8b51715c
commit
838ab6852f
@ -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
|
public Task StartAsync(CancellationToken cTok)
|
||||||
{
|
{
|
||||||
_pipeName = $"{_options.BasePipeName}.{sid}";
|
_logger.LogInformation("IPCService is starting...");
|
||||||
_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 cancellationToken)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("IPCServerService 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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private async Task<byte[]> ReadMessageAsync(PipeStream pipe, CancellationToken cTok)
|
#else
|
||||||
|
public abstract class IPCBaseClientService : IPCService
|
||||||
{
|
{
|
||||||
byte[] lengthBuffer = new byte[4];
|
private uint _id = 0;
|
||||||
int bytesRead;
|
private Response? _resp;
|
||||||
|
|
||||||
bytesRead = await pipe.ReadAsync(lengthBuffer, 0, 4, cTok);
|
protected IPCBaseClientService(ILogger logger, string name)
|
||||||
if (bytesRead == 0) throw new EndOfStreamException("Pipe closed or no data for length prefix.");
|
: base(logger, name) { }
|
||||||
if (bytesRead < 4) throw new IOException($"Failed to read full 4-byte length prefix. Read {bytesRead} bytes.");
|
|
||||||
|
|
||||||
int messageLength = BitConverter.ToInt32(lengthBuffer, 0);
|
public async Task<Response?> GetResponseAsync()
|
||||||
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 (_task == null)
|
||||||
if (bytesRead == 0) throw new EndOfStreamException($"Pipe closed unexpectedly while reading message payload. Expected {messageLength} bytes, read {totalBytesRead}.");
|
return null;
|
||||||
totalBytesRead += bytesRead;
|
|
||||||
|
await _task;
|
||||||
|
|
||||||
|
_pipe?.Dispose();
|
||||||
|
_pipe = null;
|
||||||
|
_task = null;
|
||||||
|
|
||||||
|
return _resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageBuffer;
|
private async Task SendRequestAsync(NamedPipeClientStream pipe, Request request)
|
||||||
|
{
|
||||||
|
_pipe = pipe;
|
||||||
|
await pipe.ConnectAsync(5000);
|
||||||
|
Console.WriteLine("Connected to pipe!");
|
||||||
|
|
||||||
|
if (await WriteMessageAsync(pipe, PayloadHandler.Serialize(request), _cTok?.Token ?? CancellationToken.None))
|
||||||
|
{
|
||||||
|
_resp = PayloadHandler.Deserialize<Response>(await ReadMessageAsync(pipe, _cTok?.Token ?? CancellationToken.None));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new IOException("Failed to write request into named pipe");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> WriteMessageAsync(PipeStream pipe, byte[] buffer, CancellationToken cTok)
|
public IPCBaseClientService SendRequest(int sessionId, Request request)
|
||||||
{
|
{
|
||||||
try
|
_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>
|
||||||
{
|
{
|
||||||
byte[] lengthPrefix = BitConverter.GetBytes(buffer.Length);
|
return SendRequest(sessionId, PayloadHandler.CreateRequest(++_id, type, data));
|
||||||
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.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, $"WriteMessageAsync: Error writing to pipe. Buffer size: {buffer.Length} bytes.");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
}
|
@ -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,
|
|
||||||
Success = true,
|
|
||||||
Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new ScreenshotResponse
|
|
||||||
{
|
{
|
||||||
Size = new AreaSize { Width = screenshot.Width, Height = screenshot.Height },
|
Size = new AreaSize { Width = screenshot.Width, Height = screenshot.Height },
|
||||||
Data = ByteString.CopyFrom(ms.ToArray())
|
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,
|
|
||||||
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)
|
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()
|
||||||
|
Loading…
Reference in New Issue
Block a user