using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using UnityEngine.Networking; public sealed class BriskArchiveModule : BriskModuleBase { public async Task> GetSlotsAsync() { return await ExecuteAsync(async context => { var data = await context.HttpClient.GetRawDataAsync("/archives/slots", null, true); return (IReadOnlyList)BriskModelMapper.ToArchiveSlots(data); }); } public async Task GetMetaAsync(int slotNo) { ValidateSlotNo(slotNo); return await ExecuteAsync(async context => { var data = await context.HttpClient.GetDataAsync($"/archives/slot/{slotNo}/meta", null, true); return BriskModelMapper.ToArchiveMeta(data); }); } public async Task UploadAsync(int slotNo, byte[] bytes, int? baseVersion = null, string checksum = null) { ValidateSlotNo(slotNo); if (bytes == null || bytes.Length == 0) { throw new ArgumentException("bytes is required.", nameof(bytes)); } return await ExecuteAsync(async context => { var finalChecksum = string.IsNullOrWhiteSpace(checksum) ? ComputeSha256(bytes) : checksum; var sections = new List { new MultipartFormDataSection("base_version", (baseVersion ?? 0).ToString()), new MultipartFormDataSection("checksum", finalChecksum), new MultipartFormFileSection("file", bytes, "archive.bin", "application/octet-stream") }; var data = await context.HttpClient.PostMultipartAsync($"/archives/slot/{slotNo}/upload", sections, true); return BriskModelMapper.ToArchiveUploadResult(data); }); } public async Task DownloadAsync(int slotNo) { ValidateSlotNo(slotNo); return await ExecuteAsync(async context => { var response = await context.HttpClient.GetBytesAsync($"/archives/slot/{slotNo}/download", null, true); return new BriskArchiveDownloadResult { Bytes = response.Bytes, Version = ReadHeaderInt(response.Headers, "X-Archive-Version"), Checksum = ReadHeader(response.Headers, "X-Archive-Checksum") }; }); } private static void ValidateSlotNo(int slotNo) { RequirePositive(slotNo, nameof(slotNo), "slotNo must be greater than 0."); } private static string ComputeSha256(byte[] bytes) { using (var sha = SHA256.Create()) { var hash = sha.ComputeHash(bytes); var builder = new StringBuilder(hash.Length * 2 + 7); builder.Append("sha256:"); for (var i = 0; i < hash.Length; i++) { builder.Append(hash[i].ToString("x2")); } return builder.ToString(); } } private static int ReadHeaderInt(Dictionary headers, string key) { var value = ReadHeader(headers, key); return int.TryParse(value, out var result) ? result : 0; } private static string ReadHeader(Dictionary headers, string key) { if (headers == null || string.IsNullOrWhiteSpace(key)) { return null; } foreach (var pair in headers) { if (string.Equals(pair.Key, key, StringComparison.OrdinalIgnoreCase)) { return pair.Value; } } return null; } }