up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
94
src/__Libraries/StellaOps.Audit.ReplayToken/ReplayToken.cs
Normal file
94
src/__Libraries/StellaOps.Audit.ReplayToken/ReplayToken.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace StellaOps.Audit.ReplayToken;
|
||||
|
||||
/// <summary>
|
||||
/// A deterministic, content-addressable replay token.
|
||||
/// </summary>
|
||||
public sealed class ReplayToken : IEquatable<ReplayToken>
|
||||
{
|
||||
public const string Scheme = "replay";
|
||||
public const string DefaultAlgorithm = "SHA-256";
|
||||
public const string DefaultVersion = "1.0";
|
||||
|
||||
/// <summary>
|
||||
/// The token value (SHA-256 hash in hex).
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Algorithm used for hashing.
|
||||
/// </summary>
|
||||
public string Algorithm { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Version of the token generation algorithm.
|
||||
/// </summary>
|
||||
public string Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp when token was generated.
|
||||
/// </summary>
|
||||
public DateTimeOffset GeneratedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Canonical representation for storage.
|
||||
/// </summary>
|
||||
public string Canonical => $"{Scheme}:v{Version}:{Algorithm}:{Value}";
|
||||
|
||||
public ReplayToken(string value, DateTimeOffset generatedAt, string? algorithm = null, string? version = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentException("Token value cannot be empty.", nameof(value));
|
||||
}
|
||||
|
||||
Value = value.Trim();
|
||||
GeneratedAt = generatedAt;
|
||||
Algorithm = string.IsNullOrWhiteSpace(algorithm) ? DefaultAlgorithm : algorithm.Trim();
|
||||
Version = string.IsNullOrWhiteSpace(version) ? DefaultVersion : version.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a canonical token string.
|
||||
/// </summary>
|
||||
public static ReplayToken Parse(string canonical)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(canonical))
|
||||
{
|
||||
throw new ArgumentException("Token cannot be empty.", nameof(canonical));
|
||||
}
|
||||
|
||||
var parts = canonical.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 4 || !string.Equals(parts[0], Scheme, StringComparison.Ordinal))
|
||||
{
|
||||
throw new FormatException($"Invalid replay token format: {canonical}");
|
||||
}
|
||||
|
||||
var versionPart = parts[1];
|
||||
if (!versionPart.StartsWith("v", StringComparison.Ordinal) || versionPart.Length <= 1)
|
||||
{
|
||||
throw new FormatException($"Invalid replay token version: {canonical}");
|
||||
}
|
||||
|
||||
var version = versionPart[1..];
|
||||
var algorithm = parts[2];
|
||||
var value = parts[3];
|
||||
|
||||
return new ReplayToken(value, DateTimeOffset.UnixEpoch, algorithm, version);
|
||||
}
|
||||
|
||||
public override string ToString() => Canonical;
|
||||
|
||||
public bool Equals(ReplayToken? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is ReplayToken other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
|
||||
}
|
||||
Reference in New Issue
Block a user