Add XML docs

This commit is contained in:
Stone_Red
2025-12-23 15:15:18 +01:00
parent 55f26b3a92
commit 3e339b2b7a
26 changed files with 377 additions and 4 deletions
@@ -1,8 +1,23 @@
namespace RemoteExec.Server.Configuration;
/// <summary>
/// Represents an individual API key configuration.
/// </summary>
public class ApiKeyConfiguration
{
/// <summary>
/// Gets or sets the API key value.
/// </summary>
public required string Key { get; set; }
/// <summary>
/// Gets or sets an optional description of the API key.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets whether this API key is enabled.
/// Default is true.
/// </summary>
public bool Enabled { get; set; } = true;
}
@@ -1,6 +1,12 @@
namespace RemoteExec.Server.Configuration;
/// <summary>
/// Configuration for API key authentication.
/// </summary>
public class AuthenticationConfiguration
{
/// <summary>
/// Gets or sets the list of configured API keys.
/// </summary>
public List<ApiKeyConfiguration> ApiKeys { get; set; } = [];
}
@@ -4,10 +4,18 @@ using RemoteExec.Server.Hubs;
namespace RemoteExec.Server.Controllers;
/// <summary>
/// Controller for handling assembly upload requests from clients.
/// </summary>
[ApiController]
[Route("/")]
public class AssemblyController(ILogger<AssemblyController> logger) : ControllerBase
{
/// <summary>
/// Receives an assembly binary from a client in response to an assembly request.
/// </summary>
/// <param name="requestId">The unique identifier for the assembly request.</param>
/// <returns>An action result indicating success or failure.</returns>
[HttpPost("provide-assembly")]
public async Task<IActionResult> ProvideAssembly([FromQuery] Guid requestId)
{
@@ -11,6 +11,9 @@ using System.Text.Json;
namespace RemoteExec.Server.Hubs;
/// <summary>
/// SignalR hub that handles remote method execution requests from clients.
/// </summary>
public class RemoteExecutionHub : Hub
{
private static readonly ConcurrentDictionary<string, RemoteJobAssemblyLoadContext> connections = new();
@@ -34,6 +37,12 @@ public class RemoteExecutionHub : Hub
private readonly ILogger<RemoteExecutionHub> logger;
/// <summary>
/// Initializes a new instance of the <see cref="RemoteExecutionHub"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <param name="executionOptions">The execution configuration options.</param>
/// <param name="metricsOptions">The metrics configuration options.</param>
public RemoteExecutionHub(ILogger<RemoteExecutionHub> logger, IOptions<ExecutionConfiguration> executionOptions, IOptions<MetricsConfiguration> metricsOptions)
{
this.logger = logger;
@@ -49,6 +58,7 @@ public class RemoteExecutionHub : Hub
}
}
/// <inheritdoc/>
public override Task OnConnectedAsync()
{
RemoteJobAssemblyLoadContext assemblyLoadContext = new RemoteJobAssemblyLoadContext($"RemoteJob_{Guid.NewGuid()}");
@@ -61,6 +71,7 @@ public class RemoteExecutionHub : Hub
return base.OnConnectedAsync();
}
/// <inheritdoc/>
public override Task OnDisconnectedAsync(Exception? exception)
{
if (connections.TryRemove(Context.ConnectionId, out RemoteJobAssemblyLoadContext? assemblyLoadContext))
@@ -74,6 +85,10 @@ public class RemoteExecutionHub : Hub
return base.OnDisconnectedAsync(exception);
}
/// <summary>
/// Starts processing a stream of tasks from the client.
/// </summary>
/// <param name="taskStream">The async enumerable stream of tasks to execute.</param>
public async Task StartTaskStream(IAsyncEnumerable<TaskItem> taskStream)
{
logger.LogInformation("Starting task stream for connection {ConnectionId}", Context.ConnectionId);
@@ -119,6 +134,11 @@ public class RemoteExecutionHub : Hub
}
}
/// <summary>
/// Executes a single remote method request.
/// </summary>
/// <param name="req">The execution request.</param>
/// <returns>The execution result.</returns>
public async Task<RemoteExecutionResult> Execute(RemoteExecutionRequest req)
{
return await ExecuteTask(req);
@@ -231,6 +251,11 @@ public class RemoteExecutionHub : Hub
}
}
/// <summary>
/// Provides assembly bytes to fulfill a pending assembly request.
/// </summary>
/// <param name="requestId">The unique identifier for the assembly request.</param>
/// <param name="assemblyBytes">The assembly binary data.</param>
public static async Task ProvideAssembly(Guid requestId, byte[] assemblyBytes)
{
if (pendingAssemblyRequests.TryRemove(requestId, out TaskCompletionSource<byte[]>? tcs))
@@ -239,11 +264,19 @@ public class RemoteExecutionHub : Hub
}
}
/// <summary>
/// Gets the current server metrics.
/// </summary>
/// <returns>The current server metrics.</returns>
public async Task<ServerMetrics> GetMetrics()
{
return await GetServerMetrics();
}
/// <summary>
/// Broadcasts server metrics to all connected clients if metrics have changed significantly.
/// </summary>
/// <param name="hubContext">The hub context for broadcasting.</param>
public static async Task BroadcastMetricsAsync(IHubContext<RemoteExecutionHub> hubContext)
{
ServerMetrics metrics = await GetServerMetrics();
@@ -7,12 +7,21 @@ using System.Collections.Concurrent;
namespace RemoteExec.Server.Middleware;
/// <summary>
/// Middleware that authenticates requests using API keys in the X-API-Key header.
/// </summary>
public class ApiKeyAuthenticationMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ApiKeyAuthenticationMiddleware> _logger;
private readonly ConcurrentDictionary<string, ApiKeyConfiguration> _apiKeys;
/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyAuthenticationMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="authOptions">The authentication configuration options.</param>
/// <param name="logger">The logger instance.</param>
public ApiKeyAuthenticationMiddleware(
RequestDelegate next,
IOptionsMonitor<AuthenticationConfiguration> authOptions,
@@ -66,6 +75,10 @@ public class ApiKeyAuthenticationMiddleware
_logger.LogInformation("Loaded {Count} active API keys", _apiKeys.Count);
}
/// <summary>
/// Invokes the middleware to authenticate the request.
/// </summary>
/// <param name="context">The HTTP context for the current request.</param>
public async Task InvokeAsync(HttpContext context)
{
// Skip authentication for health checks
@@ -2,6 +2,9 @@
namespace RemoteExec.Server;
/// <summary>
/// An isolated assembly load context for remote job execution, allowing assemblies to be unloaded.
/// </summary>
public class RemoteJobAssemblyLoadContext(string name) : AssemblyLoadContext(name, true)
{
}
@@ -2,20 +2,34 @@
namespace RemoteExec.Server;
/// <summary>
/// Event arguments for assembly request events.
/// </summary>
public class RequestAssemblyEventArgs(AssemblyName assemblyName) : EventArgs
{
private readonly TaskCompletionSource taskCompletionSource = new TaskCompletionSource();
private Assembly? assembly = null;
/// <summary>
/// Gets the assembly name being requested.
/// </summary>
public AssemblyName Assembly { get; } = assemblyName;
/// <summary>
/// Asynchronously waits for the assembly to be provided.
/// </summary>
/// <returns>The assembly, or null if not provided.</returns>
public async Task<Assembly?> GetAssemblyAsync()
{
await taskCompletionSource.Task;
return assembly;
}
/// <summary>
/// Sets the assembly to fulfill the request.
/// </summary>
/// <param name="assembly">The assembly to provide.</param>
public void SetAssembly(Assembly assembly)
{
this.assembly = assembly;
@@ -6,10 +6,14 @@ using RemoteExec.Server.Hubs;
namespace RemoteExec.Server.Services;
/// <summary>
/// Background service that periodically broadcasts server metrics to all connected clients.
/// </summary>
public class MetricsBroadcastService(IHubContext<RemoteExecutionHub> hubContext, ILogger<MetricsBroadcastService> logger, IOptions<MetricsConfiguration> metricsOptions) : BackgroundService
{
private readonly TimeSpan broadcastInterval = TimeSpan.FromMilliseconds(metricsOptions.Value.BroadcastIntervalMs);
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Metrics broadcast service started with interval {Interval}ms", broadcastInterval.TotalMilliseconds);