92 lines
3.8 KiB
C#
92 lines
3.8 KiB
C#
using System.IO.Pipes;
|
|
using System.Security.Principal;
|
|
using Serilog;
|
|
using Serilog.Core;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Ipc;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Ipc;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Tests;
|
|
|
|
/// <summary>
|
|
/// End-to-end IPC test: <see cref="PipeServer"/> (from Galaxy.Host) accepts a connection from
|
|
/// the Proxy's <see cref="GalaxyIpcClient"/>. Verifies the Hello handshake, shared-secret
|
|
/// check, and heartbeat round-trip. Uses the current user's SID so the ACL allows the
|
|
/// localhost test process. Skipped on non-Windows (pipe ACL is Windows-only).
|
|
/// </summary>
|
|
[Trait("Category", "Integration")]
|
|
public sealed class IpcHandshakeIntegrationTests
|
|
{
|
|
[Fact]
|
|
public async Task Hello_handshake_with_correct_secret_succeeds_and_heartbeat_round_trips()
|
|
{
|
|
if (!OperatingSystem.IsWindows()) return; // pipe ACL is Windows-only
|
|
if (IsAdministrator()) return; // ACL explicitly denies Administrators — skip on admin shells
|
|
|
|
using var currentIdentity = WindowsIdentity.GetCurrent();
|
|
var allowedSid = currentIdentity.User!;
|
|
var pipeName = $"OtOpcUaGalaxyTest-{Guid.NewGuid():N}";
|
|
const string secret = "test-secret-2026";
|
|
Logger log = new LoggerConfiguration().CreateLogger();
|
|
|
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
|
|
|
var server = new PipeServer(pipeName, allowedSid, secret, log);
|
|
var serverTask = Task.Run(() => server.RunOneConnectionAsync(new StubFrameHandler(), cts.Token));
|
|
|
|
await using var client = await GalaxyIpcClient.ConnectAsync(
|
|
pipeName, secret, TimeSpan.FromSeconds(5), cts.Token);
|
|
|
|
// Heartbeat round-trip via the stub handler.
|
|
var ack = await client.CallAsync<Heartbeat, HeartbeatAck>(
|
|
MessageKind.Heartbeat,
|
|
new Heartbeat { SequenceNumber = 42, UtcUnixMs = 1000 },
|
|
MessageKind.HeartbeatAck,
|
|
cts.Token);
|
|
ack.SequenceNumber.ShouldBe(42L);
|
|
|
|
cts.Cancel();
|
|
try { await serverTask; } catch (OperationCanceledException) { }
|
|
server.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Hello_with_wrong_secret_is_rejected()
|
|
{
|
|
if (!OperatingSystem.IsWindows()) return;
|
|
if (IsAdministrator()) return;
|
|
|
|
using var currentIdentity = WindowsIdentity.GetCurrent();
|
|
var allowedSid = currentIdentity.User!;
|
|
var pipeName = $"OtOpcUaGalaxyTest-{Guid.NewGuid():N}";
|
|
Logger log = new LoggerConfiguration().CreateLogger();
|
|
|
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
|
var server = new PipeServer(pipeName, allowedSid, "real-secret", log);
|
|
var serverTask = Task.Run(() => server.RunOneConnectionAsync(new StubFrameHandler(), cts.Token));
|
|
|
|
await Should.ThrowAsync<UnauthorizedAccessException>(() =>
|
|
GalaxyIpcClient.ConnectAsync(pipeName, "wrong-secret", TimeSpan.FromSeconds(5), cts.Token));
|
|
|
|
cts.Cancel();
|
|
try { await serverTask; } catch { /* server loop ends */ }
|
|
server.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The production ACL explicitly denies Administrators. On dev boxes the interactive user
|
|
/// is often an Administrator, so the allow rule gets overridden by the deny — the pipe
|
|
/// refuses the connection. Skip in that case; the production install runs as a dedicated
|
|
/// non-admin service account.
|
|
/// </summary>
|
|
private static bool IsAdministrator()
|
|
{
|
|
if (!OperatingSystem.IsWindows()) return false;
|
|
using var identity = WindowsIdentity.GetCurrent();
|
|
var principal = new WindowsPrincipal(identity);
|
|
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
|
}
|
|
}
|