/ GUNRPG.Infrastructure / Security / RunReplayEngine.cs
RunReplayEngine.cs
 1  using GUNRPG.Application.Backend;
 2  using GUNRPG.Core.Operators;
 3  using Microsoft.Extensions.Logging;
 4  using Microsoft.Extensions.Logging.Abstractions;
 5  
 6  namespace GUNRPG.Security;
 7  
 8  public sealed class RunReplayEngine
 9  {
10      private readonly ILogger<RunReplayEngine> _logger;
11  
12      public RunReplayEngine(ILogger<RunReplayEngine>? logger = null)
13      {
14          _logger = logger ?? NullLogger<RunReplayEngine>.Instance;
15      }
16  
17      public byte[] ValidateRunOnly(IReadOnlyList<OperatorEvent> events)
18      {
19          ArgumentNullException.ThrowIfNull(events);
20  
21          var replayedAggregate = OperatorAggregate.FromEvents(events);
22          if (replayedAggregate.Events.Count != events.Count)
23          {
24              throw new InvalidOperationException(
25                  $"Replay consumed only {replayedAggregate.Events.Count} of {events.Count} events; the event chain may be tampered.");
26          }
27  
28          return OfflineMissionHashing.ComputeReplayFinalStateHash(replayedAggregate);
29      }
30  
31      public RunValidationResult ValidateAndSignRun(
32          Guid runId,
33          Guid playerId,
34          IReadOnlyList<OperatorEvent> events,
35          ServerIdentity serverIdentity)
36      {
37          ArgumentNullException.ThrowIfNull(serverIdentity);
38  
39          var finalStateHash = ValidateRunOnly(events);
40          var attestation = new SignedRunValidation(
41              serverIdentity.SignRunValidation(runId, playerId, finalStateHash),
42              serverIdentity.Certificate);
43  
44          _logger.LogDebug("Run {RunId} validated and signed by server {ServerId}", runId, serverIdentity.Certificate.ServerId);
45  
46          return new RunValidationResult(
47              runId,
48              playerId,
49              serverIdentity.Certificate.ServerId,
50              finalStateHash,
51              attestation);
52      }
53  }