Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,104 @@
using System.Security.Cryptography;
using System.Text;
using StellaOps.AirGap.Bundle.Models;
using StellaOps.AirGap.Bundle.Serialization;
namespace StellaOps.AirGap.Bundle.Validation;
public sealed class BundleValidator : IBundleValidator
{
public async Task<BundleValidationResult> ValidateAsync(
BundleManifest manifest,
string bundlePath,
CancellationToken ct = default)
{
var errors = new List<BundleValidationError>();
var warnings = new List<BundleValidationWarning>();
if (manifest.Feeds.Length == 0)
{
errors.Add(new BundleValidationError("Feeds", "At least one feed required"));
}
if (manifest.CryptoMaterials.Length == 0)
{
errors.Add(new BundleValidationError("CryptoMaterials", "Trust roots required"));
}
foreach (var feed in manifest.Feeds)
{
var filePath = Path.Combine(bundlePath, feed.RelativePath);
var result = await VerifyFileDigestAsync(filePath, feed.Digest, ct).ConfigureAwait(false);
if (!result.IsValid)
{
errors.Add(new BundleValidationError("Feeds",
$"Feed {feed.FeedId} digest mismatch: expected {feed.Digest}, got {result.ActualDigest}"));
}
}
if (manifest.ExpiresAt.HasValue && manifest.ExpiresAt.Value < DateTimeOffset.UtcNow)
{
warnings.Add(new BundleValidationWarning("ExpiresAt", "Bundle has expired"));
}
foreach (var feed in manifest.Feeds)
{
var age = DateTimeOffset.UtcNow - feed.SnapshotAt;
if (age.TotalDays > 7)
{
warnings.Add(new BundleValidationWarning("Feeds",
$"Feed {feed.FeedId} is {age.TotalDays:F0} days old"));
}
}
if (manifest.BundleDigest is not null)
{
var computed = ComputeBundleDigest(manifest);
if (!string.Equals(computed, manifest.BundleDigest, StringComparison.OrdinalIgnoreCase))
{
errors.Add(new BundleValidationError("BundleDigest", "Bundle digest mismatch"));
}
}
return new BundleValidationResult(
errors.Count == 0,
errors,
warnings,
manifest.TotalSizeBytes);
}
private static async Task<(bool IsValid, string ActualDigest)> VerifyFileDigestAsync(
string filePath, string expectedDigest, CancellationToken ct)
{
if (!File.Exists(filePath))
{
return (false, "FILE_NOT_FOUND");
}
await using var stream = File.OpenRead(filePath);
var hash = await SHA256.HashDataAsync(stream, ct).ConfigureAwait(false);
var actualDigest = Convert.ToHexString(hash).ToLowerInvariant();
return (string.Equals(actualDigest, expectedDigest, StringComparison.OrdinalIgnoreCase), actualDigest);
}
private static string ComputeBundleDigest(BundleManifest manifest)
{
var withoutDigest = manifest with { BundleDigest = null };
var json = BundleManifestSerializer.Serialize(withoutDigest);
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(json))).ToLowerInvariant();
}
}
public interface IBundleValidator
{
Task<BundleValidationResult> ValidateAsync(BundleManifest manifest, string bundlePath, CancellationToken ct = default);
}
public sealed record BundleValidationResult(
bool IsValid,
IReadOnlyList<BundleValidationError> Errors,
IReadOnlyList<BundleValidationWarning> Warnings,
long TotalSizeBytes);
public sealed record BundleValidationError(string Component, string Message);
public sealed record BundleValidationWarning(string Component, string Message);