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? 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(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 ReadAsync(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(payload, Json) ?? throw new InvalidDataException("Administrative pipe message is empty."); } private static async Task ReadExactlyAsync(Stream stream, Memory 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; } } }