diff options
Diffstat (limited to 'src/Agent.Admin/Agent.Admin.Client/AdminProtocol.cs')
| -rw-r--r-- | src/Agent.Admin/Agent.Admin.Client/AdminProtocol.cs | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/src/Agent.Admin/Agent.Admin.Client/AdminProtocol.cs b/src/Agent.Admin/Agent.Admin.Client/AdminProtocol.cs new file mode 100644 index 0000000..679ccab --- /dev/null +++ b/src/Agent.Admin/Agent.Admin.Client/AdminProtocol.cs @@ -0,0 +1,61 @@ +using System.Buffers.Binary; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Agent.Admin.Client; + +public sealed record AdminNetworkEndpoint(string Address, int Port); + +public sealed record AdminRequest( + int Version, + string Operation, + Guid TaskId, + string? ApplicationSid = null, + string? NetworkAccess = null, + IReadOnlyList<AdminNetworkEndpoint>? Endpoints = null, + bool? OriginallyLoopbackExempt = null); + +public sealed record AdminResponse(bool Success, string? ErrorCode = null, string? Message = null, bool? LoopbackExempt = null); + +public static class AdminProtocol +{ + public const int Version = 1; + public const int MaxMessageBytes = 64 * 1024; + private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web) + { + UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow + }; + + public static async Task WriteAsync<T>(Stream stream, T value, CancellationToken cancellationToken) + { + byte[] payload = JsonSerializer.SerializeToUtf8Bytes(value, Json); + if (payload.Length is <= 0 or > MaxMessageBytes) throw new InvalidDataException("Administrative pipe message exceeds the fixed size limit."); + byte[] header = new byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(header, payload.Length); + await stream.WriteAsync(header, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(payload, cancellationToken).ConfigureAwait(false); + await stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + public static async Task<T> ReadAsync<T>(Stream stream, CancellationToken cancellationToken) + { + byte[] header = new byte[sizeof(int)]; + await ReadExactlyAsync(stream, header, cancellationToken).ConfigureAwait(false); + int length = BinaryPrimitives.ReadInt32LittleEndian(header); + if (length is <= 0 or > MaxMessageBytes) throw new InvalidDataException("Administrative pipe message has an invalid size."); + byte[] payload = new byte[length]; + await ReadExactlyAsync(stream, payload, cancellationToken).ConfigureAwait(false); + return JsonSerializer.Deserialize<T>(payload, Json) ?? throw new InvalidDataException("Administrative pipe message is empty."); + } + + private static async Task ReadExactlyAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken) + { + int offset = 0; + while (offset < buffer.Length) + { + int read = await stream.ReadAsync(buffer[offset..], cancellationToken).ConfigureAwait(false); + if (read == 0) throw new EndOfStreamException("Administrative pipe disconnected."); + offset += read; + } + } +} |
