155 lines
5.7 KiB
C#
155 lines
5.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Paca.Core.Hls;
|
|
using Paca.Core.Platform;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Core;
|
|
|
|
/// <summary>
|
|
/// Cycle 4: 극한 RED 시나리오 - 카오스 멀티스레딩 동시성 무결성 및 경로 순회(Path Traversal) / 파일 락 방어선 검증
|
|
/// </summary>
|
|
public class ConcurrencyAndCacheIntegrityExtremeRedTests
|
|
{
|
|
[Fact]
|
|
public void Cycle4_Gate1_VideoDeduplicator_ConcurrentStress_MustNotThrowOrCorrupt()
|
|
{
|
|
var deduplicator = new VideoDeduplicator(maxHammingThreshold: 5);
|
|
const int TaskCount = 32;
|
|
const int IterationsPerTask = 200;
|
|
|
|
Parallel.For(0, TaskCount, taskId =>
|
|
{
|
|
for (int i = 0; i < IterationsPerTask; i++)
|
|
{
|
|
var filePath = $"C:/media/video_{taskId}_{i}.mp4";
|
|
ulong hash = (ulong)(taskId * 10000 + i);
|
|
|
|
// 동시 등록
|
|
deduplicator.RegisterVideo(filePath, hash);
|
|
|
|
// 동시 검색
|
|
var match = deduplicator.FindDuplicate(hash);
|
|
Assert.NotNull(match);
|
|
Assert.Equal(0, match.HammingDistance);
|
|
}
|
|
});
|
|
|
|
Assert.Equal(TaskCount * IterationsPerTask, deduplicator.RegisteredCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Cycle4_Gate2_MultiSourceSegmentAccelerator_ConcurrentStress_MustNotThrow()
|
|
{
|
|
var accelerator = new MultiSourceSegmentAccelerator();
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
accelerator.AddMirror($"https://cdn{i}.example.com/hls/");
|
|
}
|
|
|
|
const int TaskCount = 32;
|
|
const int IterationsPerTask = 200;
|
|
|
|
Parallel.For(0, TaskCount, taskId =>
|
|
{
|
|
for (int i = 0; i < IterationsPerTask; i++)
|
|
{
|
|
var targetMirror = $"https://cdn{i % 5}.example.com/hls/";
|
|
if (i % 3 == 0)
|
|
{
|
|
accelerator.RecordFailure(targetMirror);
|
|
}
|
|
else if (i % 3 == 1)
|
|
{
|
|
accelerator.RecordLatency(targetMirror, TimeSpan.FromMilliseconds(20 + i));
|
|
}
|
|
else
|
|
{
|
|
var mirror = accelerator.SelectBestMirror(i);
|
|
Assert.NotEmpty(mirror);
|
|
var fastest = accelerator.GetFastestMirror();
|
|
Assert.NotNull(fastest);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Cycle4_Gate3_PredictiveChunkPrefetcher_ConcurrentStress_MustNotThrow()
|
|
{
|
|
var prefetcher = new PredictiveChunkPrefetcher(initialWindowSize: 3, minWindowSize: 1, maxWindowSize: 10, maxSamples: 10);
|
|
const int TaskCount = 32;
|
|
const int IterationsPerTask = 200;
|
|
|
|
Parallel.For(0, TaskCount, taskId =>
|
|
{
|
|
for (int i = 0; i < IterationsPerTask; i++)
|
|
{
|
|
long bytes = 1_000_000 + (taskId * 50_000);
|
|
var duration = TimeSpan.FromMilliseconds(200 + (i % 100));
|
|
|
|
prefetcher.RecordChunkDownload(bytes, duration);
|
|
var window = prefetcher.CalculateOptimalPrefetchWindow();
|
|
Assert.InRange(window, 1, 10);
|
|
}
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Cycle4_Gate4_PartialChunkIntegrityCache_PathTraversalDefense_ReconcileAndClean()
|
|
{
|
|
var tempRoot = Path.Combine(Path.GetTempPath(), "paca_traversal_" + Guid.NewGuid().ToString("N"));
|
|
var partsDir = Path.Combine(tempRoot, "parts");
|
|
Directory.CreateDirectory(partsDir);
|
|
|
|
// 상위 디렉터리에 피해를 줄 수 있는 카나리아 파일 생성
|
|
var canaryFile = Path.Combine(tempRoot, "canary_sensitive_data.txt");
|
|
File.WriteAllText(canaryFile, "CRITICAL DATA DO NOT DELETE");
|
|
|
|
try
|
|
{
|
|
var cache = new PartialChunkIntegrityCache();
|
|
|
|
// 악의적인 path traversal 청크 이름 주입 ("../../canary_sensitive_data.txt")
|
|
var report = cache.ReconcileAndClean(
|
|
partsDir,
|
|
totalSegments: 1,
|
|
getChunkName: _ => "../../canary_sensitive_data.txt"
|
|
);
|
|
|
|
// parts 디렉터리 외부의 카나리아 파일이 삭제되지 않고 온전히 보존되어야 함
|
|
Assert.True(File.Exists(canaryFile), "상위 디렉터리의 카나리아 파일이 Path Traversal 로 인해 삭제되었습니다!");
|
|
Assert.Equal("CRITICAL DATA DO NOT DELETE", File.ReadAllText(canaryFile));
|
|
}
|
|
finally
|
|
{
|
|
try { if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, true); } catch { }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Cycle4_Gate5_PartialChunkIntegrityCache_VerifyChunkFile_LockedFileDefense()
|
|
{
|
|
var tempFile = Path.Combine(Path.GetTempPath(), "paca_locked_" + Guid.NewGuid().ToString("N") + ".seg");
|
|
File.WriteAllBytes(tempFile, new byte[1024]);
|
|
|
|
try
|
|
{
|
|
var cache = new PartialChunkIntegrityCache();
|
|
|
|
// 백신 또는 외부 프로세스가 독점 락(FileShare.None)을 잡고 있는 상황 시뮬레이션
|
|
using var lockedStream = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.None);
|
|
|
|
// unhandled IOException 크래시 없이 Corrupt 또는 Partial 로 안전하게 처리되어야 함
|
|
var state = cache.VerifyChunkFile(tempFile, expectedBytes: 1024, expectedSha256: "dummy_hash");
|
|
Assert.True(state == ChunkIntegrityState.Corrupt || state == ChunkIntegrityState.Partial);
|
|
}
|
|
finally
|
|
{
|
|
try { if (File.Exists(tempFile)) File.Delete(tempFile); } catch { }
|
|
}
|
|
}
|
|
}
|