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; /// /// Cycle 4: 극한 RED 시나리오 - 카오스 멀티스레딩 동시성 무결성 및 경로 순회(Path Traversal) / 파일 락 방어선 검증 /// 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 { } } } }