// Copyright Epic Games, Inc. All Rights Reserved. using System.Buffers.Binary; using System.Text.Json; using System.Text.Json.Serialization; using EpicGames.Core; namespace HordeAgent { /// /// Constants /// public static class AgentMessagePipe { /// /// Name of the pipe to communicate over /// public const string PipeName = "Horde.Agent"; } /// /// Message /// public enum AgentMessageType { /// /// Response indicating that the sent message was invalid /// InvalidResponse = 0, /// /// Request the current agent status. /// GetStatusRequest = 1, /// /// Returns the current status, as a Json-encoded message /// GetStatusResponse = 2, /// /// Sets the paused state /// SetEnabledRequest = 3, /// /// Gets information about the server we're connected to. /// GetSettingsRequest = 4, /// /// Returns information about the server we're connected to. /// GetSettingsResponse = 5, /// /// Set agent settings request /// SetSettingsRequest = 6, /// /// Set agent settings response /// SetSettingsResponse = 7, } /// /// Buffer used to read/write agent messages /// public class AgentMessageBuffer { static readonly JsonSerializerOptions s_jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; readonly ArrayMemoryWriter _writer = new ArrayMemoryWriter(256); /// /// Type of the message currently in the buffer /// public AgentMessageType Type { get; private set; } /// /// Gets the full message data /// public ReadOnlyMemory Data => _writer.WrittenMemory; /// /// Constructor /// public AgentMessageBuffer() { _writer.Advance(4); } /// /// Sets the current message to the given type, with a json serialized body /// /// Type of the message /// The message body public void Set(AgentMessageType type, object? message = null) { _writer.Clear(); _writer.Advance(4); if (message != null) { using Utf8JsonWriter writer = new Utf8JsonWriter(_writer); JsonSerializer.Serialize(writer, message, s_jsonOptions); } Type = type; BinaryPrimitives.WriteUInt32BigEndian(_writer.WrittenSpan, ((uint)type << 24) | (uint)(_writer.Length - 4)); } /// /// Reads a message from the given stream /// /// Stream to read from /// Cancellation token for the operation public async Task TryReadAsync(Stream stream, CancellationToken cancellationToken) { _writer.Clear(); Memory headerData = _writer.GetMemoryAndAdvance(4); int readLength = await stream.ReadAsync(headerData, cancellationToken); if (readLength == 0) { return false; } if (readLength < headerData.Length) { await stream.ReadFixedLengthBytesAsync(headerData.Slice(readLength), cancellationToken); } uint header = BinaryPrimitives.ReadUInt32BigEndian(headerData.Span); Type = (AgentMessageType)(header >> 24); int length = (int)(header & 0xffffff); await stream.ReadFixedLengthBytesAsync(_writer.GetMemoryAndAdvance(length), cancellationToken); return true; } /// /// Writes the current message to a stream /// /// Stream to write to /// Cancellation token for the operation public async Task SendAsync(Stream stream, CancellationToken cancellationToken) { await stream.WriteAsync(_writer.WrittenMemory, cancellationToken); } /// /// Parse the message body as a particular type /// public T Parse() { Utf8JsonReader reader = new Utf8JsonReader(_writer.WrittenSpan.Slice(4)); return JsonSerializer.Deserialize(ref reader, s_jsonOptions)!; } } /// /// Settings for the agent /// /// Url of the Horde server public record AgentSettingsMessage(Uri? ServerUrl); /// /// Sets the enabled state for the agent /// /// Whether the agent is enabled or not public record AgentEnabledMessage(bool IsEnabled); /// /// Set settings request message /// /// Maximum number of logical CPU cores workloads should use /// CPU core multiplier applied to CPU core count setting public record AgentSetSettingsRequest(int CpuCount, double CpuMultiplier); /// /// Current status of the agent /// /// Whether the agent is healthy /// Number of leases currently being executed /// Description of the state public record class AgentStatusMessage(bool Healthy, int NumLeases, string Detail) { /// /// Static status object for starting an agent /// public const string Starting = "Starting up..."; /// /// Agent is waiting to be enrolled with the server /// public const string WaitingForEnrollment = "Waiting for enrollment..."; /// /// Agent is connecting to the server /// public const string ConnectingToServer = "Connecting to server..."; } }