Skip to content

Commit

Permalink
Added PipeMessage, IPipeException and PipeException with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cschuchardt88 committed May 4, 2024
1 parent 2b7539d commit e3cdba5
Show file tree
Hide file tree
Showing 10 changed files with 563 additions and 39 deletions.
8 changes: 8 additions & 0 deletions src/Neo.Hosting.App/Extensions/StreamExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ public static void Write<T>(this Stream stream, T value)
stream.Write(span);
}

public static void Write<T>(this Stream stream, ref T value)
where T : unmanaged
{
var tSpan = MemoryMarshal.CreateSpan(ref value, 1);
var span = MemoryMarshal.AsBytes(tSpan);
stream.Write(span);
}

public static void Write<T>(this Stream stream, T[] values)
where T : unmanaged
{
Expand Down
18 changes: 18 additions & 0 deletions src/Neo.Hosting.App/NamedPipes/Protocol/IPipeException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// IPipeException.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.Hosting.App.NamedPipes.Protocol
{
internal interface IPipeException
{
PipeException? Exception { get; }
}
}
3 changes: 2 additions & 1 deletion src/Neo.Hosting.App/NamedPipes/Protocol/IPipeMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
// modifications are permitted.

using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Neo.Hosting.App.NamedPipes.Protocol
{
internal interface IPipeMessage
{
Task CopyToAsync(Stream stream);
Task CopyToAsync(Stream stream, CancellationToken cancellationToken = default);
Task CopyFromAsync(Stream stream);
byte[] ToArray();
}
Expand Down
80 changes: 80 additions & 0 deletions src/Neo.Hosting.App/NamedPipes/Protocol/PipeException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// PipeException.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Hosting.App.Extensions;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Neo.Hosting.App.NamedPipes.Protocol
{
internal sealed class PipeException : IPipeMessage
{
public const ulong Magic = 0x4552524f524d5347ul; // ERRORMSG

public required bool IsEmpty { get; set; }
public string? Message { get; set; }
public string? StackTrace { get; set; }

public Task CopyFromAsync(Stream stream)
{
if (stream.CanRead == false)
throw new IOException();

var magic = stream.Read<ulong>();
if (magic != Magic)
throw new InvalidDataException();

IsEmpty = stream.Read<bool>();

if (IsEmpty)
return Task.CompletedTask;

Message = stream.ReadString();
StackTrace = stream.ReadString();

if (string.IsNullOrEmpty(StackTrace))
StackTrace = null;

return Task.CompletedTask;
}

public Task CopyToAsync(Stream stream, CancellationToken cancellationToken = default)
{
if (stream.CanWrite == false)
throw new IOException();

CopyToStream(stream);
return stream.FlushAsync(cancellationToken);
}

public byte[] ToArray()
{
using var ms = new MemoryStream();
CopyToStream(ms);
return ms.ToArray();
}

private void CopyToStream(Stream stream)
{
stream.Write(Magic);

if (IsEmpty)
stream.Write(true);
else
{
stream.Write(false);
stream.Write(Message!);
stream.Write(StackTrace ?? string.Empty);
}
}
}
}
88 changes: 88 additions & 0 deletions src/Neo.Hosting.App/NamedPipes/Protocol/PipeMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// PipeMessage.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Hosting.App.Extensions;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Neo.Hosting.App.NamedPipes.Protocol
{
internal sealed class PipeMessage<TMessage> : IPipeMessage, IPipeException
where TMessage : class, IPipeMessage, new()
{
public const ulong Magic = 0x4d45535341474531ul; // MESSAGE1

public TMessage Payload { get; private set; }

public PipeException Exception { get; private set; }

public PipeMessage()
{
Payload = new TMessage();
Exception = new() { IsEmpty = true };
}

public static PipeMessage<TMessage> Create(TMessage payload, Exception? exception = null) =>
new()
{
Payload = payload,
Exception = new()
{
IsEmpty = exception is null,
Message = exception?.InnerException?.Message ?? exception?.Message,
StackTrace = exception?.InnerException?.StackTrace ?? exception?.StackTrace,
},
};

public async Task CopyFromAsync(Stream stream)
{
if (stream.CanRead == false)
throw new IOException();

var magic = stream.Read<ulong>();
if (magic != Magic)
throw new InvalidDataException();

await Payload.CopyFromAsync(stream);
await Exception.CopyFromAsync(stream);
}

public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken = default)
{
if (stream.CanWrite == false)
throw new IOException();

stream.Write(Magic);

await Payload.CopyToAsync(stream, cancellationToken);
await Exception.CopyToAsync(stream, cancellationToken);
}

public byte[] ToArray()
{
using var ms = new MemoryStream();

ms.Write(Magic);

var task = Payload.CopyToAsync(ms);
if (task.IsCompleted == false)
task.RunSynchronously();

task = Exception.CopyToAsync(ms);
if (task.IsCompleted == false)
task.RunSynchronously();

return ms.ToArray();
}
}
}
16 changes: 8 additions & 8 deletions src/Neo.Hosting.App/NamedPipes/Protocol/PipeVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal sealed class PipeVersion : IPipeMessage
public PipeVersion()
{
VersionNumber = Program.ApplicationVersionNumber;
Plugins = Plugin.Plugins.ToDictionary(k => k.Name, v => v.Version, StringComparer.OrdinalIgnoreCase);
Plugins = Plugin.Plugins.ToDictionary(k => k.Name, v => v.Version, StringComparer.InvariantCultureIgnoreCase);
Platform = Environment.OSVersion.Platform;
TimeStamp = DateTime.UtcNow;
MachineName = Environment.MachineName;
Expand All @@ -65,7 +65,7 @@ public Task CopyFromAsync(Stream stream)

VersionNumber = version;

var plugins = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase);
var plugins = new Dictionary<string, Version>(StringComparer.InvariantCultureIgnoreCase);
var count = stream.Read<int>();

for (var i = 0; i < count; i++)
Expand All @@ -85,14 +85,11 @@ public Task CopyFromAsync(Stream stream)
return Task.CompletedTask;
}

public Task CopyToAsync(Stream stream)
{
CopyToStream(stream);
return stream.FlushAsync();
}

public Task CopyToAsync(Stream stream, CancellationToken cancellationToken = default)
{
if (stream.CanWrite == false)
throw new IOException();

CopyToStream(stream);
return stream.FlushAsync(cancellationToken);
}
Expand All @@ -106,6 +103,9 @@ public byte[] ToArray()

private void CopyToStream(Stream stream)
{
if (stream.CanWrite == false)
throw new IOException();

stream.Write(Magic);
stream.Write(Program.ApplicationVersionNumber);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// TestPipeException.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Akka.Util;
using Neo.Hosting.App.Extensions;
using Neo.Hosting.App.NamedPipes.Protocol;
using System.Diagnostics;
using Xunit.Abstractions;

namespace Neo.Hosting.App.Tests.NamedPipes.Protocol
{
public class TestPipeException
(ITestOutputHelper testOutputHelper)
{
private static readonly string s_exceptionMessage = "Test1";
private static readonly string s_exceptionStackTrace = "Test2";

private readonly ITestOutputHelper _testOutputHelper = testOutputHelper;

[Fact]
public async Task IPipeMessage_CopyFromAsync()
{
var exception1 = new PipeException()
{
IsEmpty = false,
Message = s_exceptionMessage,
StackTrace = s_exceptionStackTrace
};
var expectedBytes = exception1.ToArray();
var expectedHexString = Convert.ToHexString(expectedBytes);

using var ms1 = new MemoryStream();
await exception1.CopyToAsync(ms1).DefaultTimeout();

var exception2 = new PipeException() { IsEmpty = true };
ms1.Position = 0;
await exception2.CopyFromAsync(ms1).DefaultTimeout();

var actualBytes = exception2.ToArray();
var actualHexString = Convert.ToHexString(actualBytes);

var className = nameof(PipeVersion);
var methodName = nameof(PipeVersion.CopyFromAsync);
_testOutputHelper.WriteLine(nameof(Debug).PadCenter(17, '-'));
_testOutputHelper.WriteLine($" Class: {className}");
_testOutputHelper.WriteLine($" Method: {methodName}");

_testOutputHelper.WriteLine(nameof(Result).PadCenter(17, '-'));
_testOutputHelper.WriteLine($" Actual: {actualHexString}");
_testOutputHelper.WriteLine($" Expected: {expectedHexString}");
_testOutputHelper.WriteLine($"-----------------");

Assert.Equal(expectedBytes, actualBytes);
Assert.Equal(exception1.IsEmpty, exception2.IsEmpty);
Assert.Equal(exception1.Message, exception2.Message);
Assert.Equal(exception1.StackTrace, exception2.StackTrace);
}

[Fact]
public async Task IPipeMessage_CopyToAsync()
{
var exception = new PipeException()
{
IsEmpty = false,
Message = s_exceptionMessage,
StackTrace = s_exceptionStackTrace
};
var expectedBytes = exception.ToArray();
var expectedHexString = Convert.ToHexString(expectedBytes);

using var ms = new MemoryStream();
await exception.CopyToAsync(ms).DefaultTimeout();

var actualBytes = ms.ToArray();
var actualHexString = Convert.ToHexString(actualBytes);

var className = nameof(PipeVersion);
var methodName = nameof(PipeVersion.CopyToAsync);
_testOutputHelper.WriteLine(nameof(Debug).PadCenter(17, '-'));
_testOutputHelper.WriteLine($" Class: {className}");
_testOutputHelper.WriteLine($" Method: {methodName}");

_testOutputHelper.WriteLine(nameof(Result).PadCenter(17, '-'));
_testOutputHelper.WriteLine($" Actual: {actualHexString}");
_testOutputHelper.WriteLine($" Expected: {expectedHexString}");
_testOutputHelper.WriteLine($"-----------------");

Assert.Equal(expectedBytes, actualBytes);
}
}
}
Loading

0 comments on commit e3cdba5

Please sign in to comment.