fix
This commit is contained in:
commit
d6325ee129
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/.*/
|
||||||
|
/bin/
|
||||||
|
/obj/
|
||||||
|
/Properties/
|
||||||
|
/*.Development.json
|
||||||
|
/*.csproj.user
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Lib/winipc-ua"]
|
||||||
|
path = Lib/winipc-ua
|
||||||
|
url = https://dev.lirent.ru/lirent/winipc-ua.git
|
||||||
7
Config/AppConfig.cs
Normal file
7
Config/AppConfig.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace WinIPC.Config
|
||||||
|
{
|
||||||
|
public class AppConfig
|
||||||
|
{
|
||||||
|
public IPCServiceOptions IPCService { get; set; } = new IPCServiceOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Lib/winipc-ua
Submodule
1
Lib/winipc-ua
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 4a8b51715c24f2fa0eb0313c56c6a565abe89806
|
||||||
327
Program.cs
Normal file
327
Program.cs
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static System.Net.Mime.MediaTypeNames;
|
||||||
|
//using WinIPC.Utils
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WinIPC.Models; // Предполагается, что сгенерированные Protobuf-классы находятся здесь
|
||||||
|
|
||||||
|
namespace IpcCliClient
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
private static readonly string _pipeName = "Global\\ProcessMonitoringService.UserAgent.Pipe";
|
||||||
|
private static uint _requestId = 0;
|
||||||
|
|
||||||
|
static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Клиентское приложение для тестирования IPC.");
|
||||||
|
Console.WriteLine("Введите команду (например, get_windows) или 'exit' для выхода.");
|
||||||
|
Console.WriteLine("---------------------------------------------");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Console.Write("> ");
|
||||||
|
string command = Console.ReadLine()?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(command))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ExecuteCommand(command);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Ошибка: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Приложение завершено.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет команду, введенную пользователем.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task ExecuteCommand(string command)
|
||||||
|
{
|
||||||
|
Request request = new Request();
|
||||||
|
string responseMessage = string.Empty;
|
||||||
|
bool handled = true;
|
||||||
|
|
||||||
|
switch (command.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "get_windows":
|
||||||
|
request.Id = ++_requestId;
|
||||||
|
request.Type = CommandType.GetWindowsInfo;
|
||||||
|
Console.WriteLine("Отправка запроса на получение списка окон...");
|
||||||
|
responseMessage = "Список окон:";
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO: Добавить другие команды (get_screenshot, get_pixel_color, input_action)
|
||||||
|
// как только будут определены их структуры запросов/ответов.
|
||||||
|
|
||||||
|
default:
|
||||||
|
Console.WriteLine($"Неизвестная команда: '{command}'");
|
||||||
|
handled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled)
|
||||||
|
{
|
||||||
|
await SendAndReceive(request, responseMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отправляет запрос и получает ответ от сервера.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task SendAndReceive(Request request, string successMessage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var pipeClient = new NamedPipeClientStream(".", _pipeName, PipeDirection.InOut))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Подключение к IPC серверу...");
|
||||||
|
await pipeClient.ConnectAsync(5000); // Таймаут 5 секунд
|
||||||
|
Console.WriteLine("Подключено.");
|
||||||
|
|
||||||
|
// Сериализация и отправка запроса
|
||||||
|
byte[] requestBytes = request.ToByteArray();
|
||||||
|
await WriteMessageAsync(pipeClient, requestBytes, CancellationToken.None);
|
||||||
|
|
||||||
|
// Чтение ответа
|
||||||
|
byte[] responseBytes = await ReadMessageAsync(pipeClient, CancellationToken.None);
|
||||||
|
var response = Response.Parser.ParseFrom(responseBytes);
|
||||||
|
|
||||||
|
// Обработка ответа
|
||||||
|
if (response.Success)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Успешно: {successMessage}");
|
||||||
|
if (response.Type == CommandType.GetWindowsInfo && response.Payload != null)
|
||||||
|
{
|
||||||
|
var windowsResponse = WindowsResponse.Parser.ParseFrom(response.Payload);
|
||||||
|
Console.WriteLine($"Найдено {windowsResponse.Data.Count} окон:");
|
||||||
|
foreach (var window in windowsResponse.Data)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - HWND: {window.Hwnd}, PID: {window.Pid}, Title: '{window.Title}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Получен успешный ответ. Сообщение: {response.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Ошибка ответа: {response.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Ошибка: Таймаут подключения к серверу.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Ошибка при взаимодействии с сервером: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Читает сообщение из pipe.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task<byte[]> ReadMessageAsync(PipeStream pipe, CancellationToken cTok)
|
||||||
|
{
|
||||||
|
// Чтение префикса длины (4 байта)
|
||||||
|
byte[] lengthBytes = new byte[4];
|
||||||
|
int bytesRead = await pipe.ReadAsync(lengthBytes, 0, 4, cTok);
|
||||||
|
if (bytesRead < 4)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("Поток закрыт или достигнут конец потока при чтении длины сообщения.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int messageLength = BitConverter.ToInt32(lengthBytes, 0);
|
||||||
|
|
||||||
|
if (messageLength <= 0)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException($"Получена некорректная длина сообщения: {messageLength}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Чтение самого сообщения
|
||||||
|
byte[] messageBytes = new byte[messageLength];
|
||||||
|
bytesRead = 0;
|
||||||
|
while (bytesRead < messageLength)
|
||||||
|
{
|
||||||
|
int read = await pipe.ReadAsync(messageBytes, bytesRead, messageLength - bytesRead, cTok);
|
||||||
|
if (read == 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException($"Поток закрыт или достигнут конец потока при чтении полезной нагрузки. Ожидалось {messageLength} байт, прочитано {bytesRead}.");
|
||||||
|
}
|
||||||
|
bytesRead += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Записывает сообщение в pipe.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task WriteMessageAsync(PipeStream pipe, byte[] messageBytes, CancellationToken cTok)
|
||||||
|
{
|
||||||
|
byte[] lengthPrefix = BitConverter.GetBytes(messageBytes.Length);
|
||||||
|
|
||||||
|
await pipe.WriteAsync(lengthPrefix, 0, lengthPrefix.Length, cTok);
|
||||||
|
await pipe.WriteAsync(messageBytes, 0, messageBytes.Length, cTok);
|
||||||
|
await pipe.FlushAsync(cTok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public class Datagram
|
||||||
|
{
|
||||||
|
public IPCWindowInfo Info;
|
||||||
|
public string Title { get; set; } = String.Empty;
|
||||||
|
public int Hwnd { get => Info.Hwnd; }
|
||||||
|
public uint Pid { get => Info.Pid; }
|
||||||
|
public uint ThreadId { get => Info.ThreadId; }
|
||||||
|
public bool IsActive { get => Info.IsActive; }
|
||||||
|
public int CursorX { get => Info.CursorX; }
|
||||||
|
public int CursorY { get => Info.CursorY; }
|
||||||
|
public int ContentWidth { get => Info.ContentWidth; }
|
||||||
|
public int ContentHeight { get => Info.ContentHeight; }
|
||||||
|
public int WindowX { get => Info.WindowX; }
|
||||||
|
public int WindowY { get => Info.WindowY; }
|
||||||
|
public int WindowWidth { get => Info.WindowWidth; }
|
||||||
|
public int WindowHeight { get => Info.WindowHeight; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("IPC Client started. Press any key to send a 'GetWindows' request...");
|
||||||
|
Console.ReadKey();
|
||||||
|
|
||||||
|
await SendGetWindowsRequest();
|
||||||
|
|
||||||
|
Console.WriteLine("\nPress any key to exit.");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SendGetWindowsRequest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var pipe = new NamedPipeClientStream(
|
||||||
|
".", // Сервер находится на локальной машине
|
||||||
|
IPC.PipeName,
|
||||||
|
PipeDirection.InOut,
|
||||||
|
PipeOptions.Asynchronous,
|
||||||
|
TokenImpersonationLevel.None,
|
||||||
|
HandleInheritability.None))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Connecting to pipe: {IPC.PipeName}...");
|
||||||
|
await pipe.ConnectAsync(5000); // Таймаут 5 секунд
|
||||||
|
Console.WriteLine("Connected to pipe!");
|
||||||
|
|
||||||
|
IPCPayload payload;
|
||||||
|
|
||||||
|
#if WINDOWS
|
||||||
|
if (IPC.SerializeIPC(new IPCHeader { Type = IPCType.ListWindows }, 0, out var data) > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Writing to pipe...");
|
||||||
|
if (await IPC.WriteMessageAsync(pipe, data, CancellationToken.None))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Writing successful");
|
||||||
|
Console.WriteLine($"Reading from pipe...");
|
||||||
|
if ((payload = await IPC.ReadMessageAsync(pipe, CancellationToken.None)).Header.Type == (IPCType.Response | IPCType.ListWindows))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Reading successful");
|
||||||
|
var s = (IEnumerable<IPCWindowInfoPayload>?)payload.Data;
|
||||||
|
var d = new List<Datagram>();
|
||||||
|
|
||||||
|
if (s != null) {
|
||||||
|
foreach (var p in s)
|
||||||
|
{
|
||||||
|
d.Add(new Datagram { Info = p.Info, Title = p.Title });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Received response: {JsonSerializer.Serialize(d)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Not received! ID: {payload.Header.ID}, Code: {payload.Header.Code}, Type: {payload.Header.Type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if IMAGE
|
||||||
|
if (IPC.SerializeIPC(new IPCHeader { Type = IPCType.GetScreenshot }, 0, out var d1) > 0 && IPC.SerializeIPC(new IPCScreenshotRequest { Hwnd = 66982 }, 0, out var d2) > 0)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[d1.Length + d2.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(d1, 0, data, 0, d1.Length);
|
||||||
|
Buffer.BlockCopy(d2, 0, data, d1.Length, d2.Length);
|
||||||
|
|
||||||
|
Console.WriteLine($"Writing to pipe...");
|
||||||
|
if (await IPC.WriteMessageAsync(pipe, data, CancellationToken.None))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Writing successful");
|
||||||
|
Console.WriteLine($"Reading from pipe...");
|
||||||
|
if ((payload = await IPC.ReadMessageAsync(pipe, CancellationToken.None)).Header.Type == (IPCType.Response | IPCType.GetScreenshot))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Reading successful");
|
||||||
|
var s = (IPCScreenshotPayload?)payload.Data;
|
||||||
|
var d = new List<Datagram>();
|
||||||
|
|
||||||
|
if (s != null)
|
||||||
|
{
|
||||||
|
using (FileStream fs = new FileStream("C:\\Users\\lirent\\test.png", FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
|
{
|
||||||
|
await fs.WriteAsync(s.Value.Image, 0, s.Value.Image.Length);
|
||||||
|
}
|
||||||
|
Console.WriteLine("See C:\\Users\\lirent\\test.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Not received! ID: {payload.Header.ID}, Code: {payload.Header.Code}, Type: {payload.Header.Type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Connection to pipe timed out. Ensure UserSessionAgent is running.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||||
|
Console.WriteLine(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
267
Services/IPCClientService.cs
Normal file
267
Services/IPCClientService.cs
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
/* This software is licensed by the MIT License, see LICENSE file */
|
||||||
|
/* Copyright © 2024-2025 Gregory Lirent */
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using WinIPC.Config;
|
||||||
|
using WinIPC.Models;
|
||||||
|
using WinIPC.Utils;
|
||||||
|
|
||||||
|
namespace WinIPC.Services
|
||||||
|
{
|
||||||
|
public class IPCClientService : IHostedService, IDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger<IPCClientService> _logger;
|
||||||
|
private readonly IPCServiceOptions _options;
|
||||||
|
private readonly string _pipeName;
|
||||||
|
|
||||||
|
private NamedPipeServerStream? _pipe;
|
||||||
|
private CancellationTokenSource? _cTok;
|
||||||
|
private Task? _task;
|
||||||
|
|
||||||
|
private Func<Request, Task<Response>>? _handler;
|
||||||
|
|
||||||
|
public IPCClientService(ILogger<IPCClientService> logger, IOptions<AppConfig> appConfigOptions)
|
||||||
|
{
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_options = appConfigOptions?.Value?.IPCService ?? throw new ArgumentNullException(nameof(appConfigOptions));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("IPCServerService is starting...");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("IPCServerService is stopping...");
|
||||||
|
|
||||||
|
_cTok?.Cancel();
|
||||||
|
if (_task != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _task;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { /* Expected */ }
|
||||||
|
catch (Exception ex) { _logger.LogError(ex, "Error while waiting for server listening task to complete."); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
_logger.LogInformation("IPCServerService stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cTok?.Dispose();
|
||||||
|
_pipe?.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartListening(Func<Request, Task<Response>> handler, CancellationToken cTok)
|
||||||
|
{
|
||||||
|
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
|
||||||
|
_cTok = CancellationTokenSource.CreateLinkedTokenSource(cTok);
|
||||||
|
_task = Task.Run(() => ServerListenLoop(_cTok.Token), _cTok.Token);
|
||||||
|
return _task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ServerListenLoop(CancellationToken cTok)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Server: Starting listen loop on pipe '{_pipeName}'...");
|
||||||
|
while (!cTok.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pipe = new NamedPipeServerStream(
|
||||||
|
_pipeName,
|
||||||
|
PipeDirection.InOut,
|
||||||
|
NamedPipeServerStream.MaxAllowedServerInstances,
|
||||||
|
PipeTransmissionMode.Byte,
|
||||||
|
PipeOptions.Asynchronous);
|
||||||
|
|
||||||
|
_logger.LogInformation("Server: Waiting for client connection...");
|
||||||
|
await _pipe.WaitForConnectionAsync(cTok);
|
||||||
|
_logger.LogInformation("Server: Client connected.");
|
||||||
|
|
||||||
|
_ = HandleClientConnectionAsync(_pipe, cTok);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Server: Listen loop cancelled.");
|
||||||
|
_pipe?.Dispose();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Server: Error in main listen loop. Disposing current pipe and continuing to next iteration.");
|
||||||
|
_pipe?.Dispose();
|
||||||
|
await Task.Delay(1000, cTok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Server: Listen loop finished.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleClientConnectionAsync(NamedPipeServerStream pipe, CancellationToken cTok)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Server: Handling connection from pipe '{_pipeName}'...");
|
||||||
|
|
||||||
|
while (pipe.IsConnected && !cTok.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
byte[]? messageBytes = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
messageBytes = await ReadMessageAsync(pipe, cTok);
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Server: Client disconnected from pipe '{_pipeName}'.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Server: Message reading cancelled for pipe '{_pipeName}'.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Server: Error reading message from pipe '{_pipeName}'.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageBytes == null || messageBytes.Length == 0)
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"Server: Received empty or null message from pipe '{_pipeName}'. Continuing...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug($"Server: Received {messageBytes.Length} bytes from pipe '{_pipeName}'.");
|
||||||
|
|
||||||
|
Response response = await ProcessIncomingRequest(messageBytes);
|
||||||
|
|
||||||
|
bool writeSuccess = await WriteMessageAsync(pipe, PayloadHandler.Serialize(response), cTok);
|
||||||
|
if (!writeSuccess)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Server: Failed to write response to pipe '{_pipeName}'.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Server: Unhandled exception in client connection handler for pipe '{_pipeName}'.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Server: Client connection handler finished for pipe '{_pipeName}'. Disposing pipe.");
|
||||||
|
pipe.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Response> ProcessIncomingRequest(byte[] messageBytes)
|
||||||
|
{
|
||||||
|
Request request;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
request = PayloadHandler.Deserialize<Request>(messageBytes);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Server: Failed to deserialize incoming message as Request. Returning error response.");
|
||||||
|
return PayloadHandler.CreateErrorResponse(0, $"Failed to deserialize request: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Id > 0 && request.Type != CommandType.UnknownCommandType)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Server: Received request ID {RequestId}, Type: {CommandType}.", request.Id, request.Type);
|
||||||
|
|
||||||
|
if (_handler != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _handler(request);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Server: Error processing request ID {request.Id}, Type {request.Type} by handler.");
|
||||||
|
return PayloadHandler.CreateErrorResponse(request.Id, $"Handler error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Server: No request handler registered for IPCServerService.");
|
||||||
|
return PayloadHandler.CreateErrorResponse(request.Id, "No handler registered on server.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Server: Received invalid Protobuf request (ID or Type missing). Raw bytes: {Bytes}", BitConverter.ToString(messageBytes));
|
||||||
|
return PayloadHandler.CreateErrorResponse(request.Id, "Invalid request format.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"WriteMessageAsync: Error writing to pipe. Buffer size: {buffer.Length} bytes.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
winipc-ua-test.csproj
Normal file
34
winipc-ua-test.csproj
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
|
||||||
|
<RootNamespace>WinIPC</RootNamespace>
|
||||||
|
<AssemblyName>UserAgentTest</AssemblyName>
|
||||||
|
|
||||||
|
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.0.0</FileVersion>
|
||||||
|
<Version>0.1.0</Version>
|
||||||
|
<Company>OpenSource</Company>
|
||||||
|
<NeutralLanguage>en-US</NeutralLanguage>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
|
||||||
|
<PackageReference Include="System.Management" Version="8.0.0" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.27.2" />
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.63.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="Lib\winipc-ua\Models\ipc.proto" GrpcServices="None" ProtoRoot="." />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
25
winipc-ua-test.sln
Normal file
25
winipc-ua-test.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.14.36221.1 d17.14
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winipc-ua-test", "winipc-ua-test.csproj", "{25044D91-C896-2DFB-8299-FBDF8F1D02EA}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{25044D91-C896-2DFB-8299-FBDF8F1D02EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{25044D91-C896-2DFB-8299-FBDF8F1D02EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{25044D91-C896-2DFB-8299-FBDF8F1D02EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{25044D91-C896-2DFB-8299-FBDF8F1D02EA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {7EC6842F-AE05-44E2-82E2-B13E5A19C4B5}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
Loading…
Reference in New Issue
Block a user