Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea

This commit is contained in:
yuanyuanxiang
2026-04-19 19:55:01 +02:00
commit 5a325a202b
744 changed files with 235562 additions and 0 deletions

View File

@@ -0,0 +1,629 @@
/**
* @file ChunkManagerTest.cpp
* @brief 分块管理和区间跟踪测试
*
* 测试覆盖:
* - FileRangeV2 区间操作
* - 接收状态跟踪
* - 区间合并算法
* - 断点续传区间计算
*/
#include <gtest/gtest.h>
#include <vector>
#include <algorithm>
#include <cstdint>
// ============================================
// 区间结构(测试专用)
// ============================================
struct Range {
uint64_t offset;
uint64_t length;
Range(uint64_t o = 0, uint64_t l = 0) : offset(o), length(l) {}
uint64_t end() const { return offset + length; }
bool operator<(const Range& other) const {
return offset < other.offset;
}
bool operator==(const Range& other) const {
return offset == other.offset && length == other.length;
}
};
// ============================================
// 区间管理类(断点续传核心逻辑)
// ============================================
class RangeManager {
public:
RangeManager(uint64_t fileSize = 0) : m_fileSize(fileSize), m_receivedBytes(0) {}
// 添加已接收区间
void AddRange(uint64_t offset, uint64_t length) {
if (length == 0) return;
Range newRange(offset, length);
m_ranges.push_back(newRange);
MergeRanges();
UpdateReceivedBytes();
}
// 获取已接收区间列表
const std::vector<Range>& GetRanges() const {
return m_ranges;
}
// 获取缺失区间列表
std::vector<Range> GetMissingRanges() const {
std::vector<Range> missing;
if (m_fileSize == 0) return missing;
uint64_t currentPos = 0;
for (const auto& r : m_ranges) {
if (r.offset > currentPos) {
missing.emplace_back(currentPos, r.offset - currentPos);
}
currentPos = r.end();
}
if (currentPos < m_fileSize) {
missing.emplace_back(currentPos, m_fileSize - currentPos);
}
return missing;
}
// 获取已接收字节数
uint64_t GetReceivedBytes() const {
return m_receivedBytes;
}
// 是否完整接收
bool IsComplete() const {
return m_receivedBytes >= m_fileSize && m_fileSize > 0;
}
// 清空
void Clear() {
m_ranges.clear();
m_receivedBytes = 0;
}
// 设置文件大小
void SetFileSize(uint64_t size) {
m_fileSize = size;
}
uint64_t GetFileSize() const {
return m_fileSize;
}
private:
void MergeRanges() {
if (m_ranges.size() <= 1) return;
std::sort(m_ranges.begin(), m_ranges.end());
std::vector<Range> merged;
merged.push_back(m_ranges[0]);
for (size_t i = 1; i < m_ranges.size(); ++i) {
Range& last = merged.back();
const Range& current = m_ranges[i];
// 检查是否可以合并(相邻或重叠)
if (current.offset <= last.end()) {
// 扩展现有区间
uint64_t newEnd = std::max(last.end(), current.end());
last.length = newEnd - last.offset;
} else {
// 新的独立区间
merged.push_back(current);
}
}
m_ranges = std::move(merged);
}
void UpdateReceivedBytes() {
m_receivedBytes = 0;
for (const auto& r : m_ranges) {
m_receivedBytes += r.length;
}
}
std::vector<Range> m_ranges;
uint64_t m_fileSize;
uint64_t m_receivedBytes;
};
// ============================================
// 接收状态类(模拟 FileRecvStateV2
// ============================================
class FileRecvState {
public:
FileRecvState() : m_fileSize(0), m_fileIndex(0), m_transferID(0) {}
void Initialize(uint64_t transferID, uint32_t fileIndex, uint64_t fileSize, const std::string& filePath) {
m_transferID = transferID;
m_fileIndex = fileIndex;
m_fileSize = fileSize;
m_filePath = filePath;
m_rangeManager.SetFileSize(fileSize);
}
void AddChunk(uint64_t offset, uint64_t length) {
m_rangeManager.AddRange(offset, length);
}
bool IsComplete() const {
return m_rangeManager.IsComplete();
}
uint64_t GetReceivedBytes() const {
return m_rangeManager.GetReceivedBytes();
}
double GetProgress() const {
if (m_fileSize == 0) return 0.0;
return static_cast<double>(GetReceivedBytes()) / m_fileSize * 100.0;
}
std::vector<Range> GetMissingRanges() const {
return m_rangeManager.GetMissingRanges();
}
const std::vector<Range>& GetReceivedRanges() const {
return m_rangeManager.GetRanges();
}
uint64_t GetTransferID() const { return m_transferID; }
uint32_t GetFileIndex() const { return m_fileIndex; }
uint64_t GetFileSize() const { return m_fileSize; }
const std::string& GetFilePath() const { return m_filePath; }
private:
uint64_t m_transferID;
uint32_t m_fileIndex;
uint64_t m_fileSize;
std::string m_filePath;
RangeManager m_rangeManager;
};
// ============================================
// Range 基础测试
// ============================================
class RangeTest : public ::testing::Test {};
TEST_F(RangeTest, DefaultConstruction) {
Range r;
EXPECT_EQ(r.offset, 0u);
EXPECT_EQ(r.length, 0u);
EXPECT_EQ(r.end(), 0u);
}
TEST_F(RangeTest, Construction) {
Range r(100, 200);
EXPECT_EQ(r.offset, 100u);
EXPECT_EQ(r.length, 200u);
EXPECT_EQ(r.end(), 300u);
}
TEST_F(RangeTest, Comparison) {
Range r1(0, 100);
Range r2(100, 100);
Range r3(0, 100);
EXPECT_TRUE(r1 < r2);
EXPECT_FALSE(r2 < r1);
EXPECT_TRUE(r1 == r3);
EXPECT_FALSE(r1 == r2);
}
// ============================================
// RangeManager 测试
// ============================================
class RangeManagerTest : public ::testing::Test {};
TEST_F(RangeManagerTest, InitialState) {
RangeManager rm(1000);
EXPECT_EQ(rm.GetReceivedBytes(), 0u);
EXPECT_EQ(rm.GetFileSize(), 1000u);
EXPECT_FALSE(rm.IsComplete());
EXPECT_TRUE(rm.GetRanges().empty());
}
TEST_F(RangeManagerTest, AddSingleRange) {
RangeManager rm(1000);
rm.AddRange(0, 500);
EXPECT_EQ(rm.GetReceivedBytes(), 500u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 500u);
}
TEST_F(RangeManagerTest, AddZeroLengthRange) {
RangeManager rm(1000);
rm.AddRange(0, 0);
EXPECT_EQ(rm.GetReceivedBytes(), 0u);
EXPECT_TRUE(rm.GetRanges().empty());
}
TEST_F(RangeManagerTest, AddNonOverlappingRanges) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(200, 100);
rm.AddRange(400, 100);
EXPECT_EQ(rm.GetReceivedBytes(), 300u);
ASSERT_EQ(rm.GetRanges().size(), 3u);
}
TEST_F(RangeManagerTest, MergeAdjacentRanges) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(100, 100); // 紧邻
EXPECT_EQ(rm.GetReceivedBytes(), 200u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 200u);
}
TEST_F(RangeManagerTest, MergeOverlappingRanges) {
RangeManager rm(1000);
rm.AddRange(0, 150);
rm.AddRange(100, 150); // 重叠
EXPECT_EQ(rm.GetReceivedBytes(), 250u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 250u);
}
TEST_F(RangeManagerTest, MergeContainedRange) {
RangeManager rm(1000);
rm.AddRange(0, 500);
rm.AddRange(100, 100); // 完全被包含
EXPECT_EQ(rm.GetReceivedBytes(), 500u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].length, 500u);
}
TEST_F(RangeManagerTest, MergeOutOfOrder) {
RangeManager rm(1000);
rm.AddRange(500, 100);
rm.AddRange(100, 100);
rm.AddRange(0, 100);
EXPECT_EQ(rm.GetReceivedBytes(), 300u);
// 0-100 和 100-200 被合并成 0-200加上 500-600共 2 个区间
ASSERT_EQ(rm.GetRanges().size(), 2u);
// 验证排序和合并
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 200u); // 合并后
EXPECT_EQ(rm.GetRanges()[1].offset, 500u);
EXPECT_EQ(rm.GetRanges()[1].length, 100u);
}
TEST_F(RangeManagerTest, MergeMultipleOverlapping) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(200, 100);
rm.AddRange(50, 200); // 跨越两个区间
EXPECT_EQ(rm.GetReceivedBytes(), 300u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetRanges()[0].offset, 0u);
EXPECT_EQ(rm.GetRanges()[0].length, 300u);
}
TEST_F(RangeManagerTest, GetMissingRanges_Empty) {
RangeManager rm(1000);
auto missing = rm.GetMissingRanges();
ASSERT_EQ(missing.size(), 1u);
EXPECT_EQ(missing[0].offset, 0u);
EXPECT_EQ(missing[0].length, 1000u);
}
TEST_F(RangeManagerTest, GetMissingRanges_Partial) {
RangeManager rm(1000);
rm.AddRange(0, 100);
rm.AddRange(500, 100);
auto missing = rm.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
EXPECT_EQ(missing[0].offset, 100u);
EXPECT_EQ(missing[0].length, 400u);
EXPECT_EQ(missing[1].offset, 600u);
EXPECT_EQ(missing[1].length, 400u);
}
TEST_F(RangeManagerTest, GetMissingRanges_Complete) {
RangeManager rm(1000);
rm.AddRange(0, 1000);
auto missing = rm.GetMissingRanges();
EXPECT_TRUE(missing.empty());
}
TEST_F(RangeManagerTest, IsComplete_Exact) {
RangeManager rm(1000);
rm.AddRange(0, 1000);
EXPECT_TRUE(rm.IsComplete());
}
TEST_F(RangeManagerTest, IsComplete_OverReceived) {
RangeManager rm(1000);
rm.AddRange(0, 1500); // 超过文件大小
EXPECT_TRUE(rm.IsComplete());
}
TEST_F(RangeManagerTest, IsComplete_Partial) {
RangeManager rm(1000);
rm.AddRange(0, 999);
EXPECT_FALSE(rm.IsComplete());
}
TEST_F(RangeManagerTest, Clear) {
RangeManager rm(1000);
rm.AddRange(0, 500);
rm.Clear();
EXPECT_EQ(rm.GetReceivedBytes(), 0u);
EXPECT_TRUE(rm.GetRanges().empty());
}
// ============================================
// FileRecvState 测试
// ============================================
class FileRecvStateTest : public ::testing::Test {};
TEST_F(FileRecvStateTest, Initialize) {
FileRecvState state;
state.Initialize(12345, 0, 1024, "C:\\test\\file.txt");
EXPECT_EQ(state.GetTransferID(), 12345u);
EXPECT_EQ(state.GetFileIndex(), 0u);
EXPECT_EQ(state.GetFileSize(), 1024u);
EXPECT_EQ(state.GetFilePath(), "C:\\test\\file.txt");
EXPECT_EQ(state.GetReceivedBytes(), 0u);
EXPECT_FALSE(state.IsComplete());
}
TEST_F(FileRecvStateTest, AddChunks) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
state.AddChunk(0, 100);
EXPECT_EQ(state.GetReceivedBytes(), 100u);
EXPECT_NEAR(state.GetProgress(), 10.0, 0.01);
state.AddChunk(100, 400);
EXPECT_EQ(state.GetReceivedBytes(), 500u);
EXPECT_NEAR(state.GetProgress(), 50.0, 0.01);
state.AddChunk(500, 500);
EXPECT_EQ(state.GetReceivedBytes(), 1000u);
EXPECT_TRUE(state.IsComplete());
EXPECT_NEAR(state.GetProgress(), 100.0, 0.01);
}
TEST_F(FileRecvStateTest, OutOfOrderChunks) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
state.AddChunk(500, 200);
state.AddChunk(0, 200);
state.AddChunk(800, 200);
EXPECT_EQ(state.GetReceivedBytes(), 600u);
auto missing = state.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
EXPECT_EQ(missing[0].offset, 200u);
EXPECT_EQ(missing[0].length, 300u);
EXPECT_EQ(missing[1].offset, 700u);
EXPECT_EQ(missing[1].length, 100u);
}
TEST_F(FileRecvStateTest, DuplicateChunks) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
state.AddChunk(0, 500);
state.AddChunk(0, 500); // 重复
state.AddChunk(250, 250); // 重叠
EXPECT_EQ(state.GetReceivedBytes(), 500u);
}
// ============================================
// 断点续传场景测试
// ============================================
class ResumeScenarioTest : public ::testing::Test {};
TEST_F(ResumeScenarioTest, SimulateInterruptedTransfer) {
FileRecvState state;
state.Initialize(12345, 0, 10000, "large_file.bin");
// 模拟接收了一些数据后中断
state.AddChunk(0, 2000);
state.AddChunk(2000, 2000);
state.AddChunk(5000, 1000);
EXPECT_EQ(state.GetReceivedBytes(), 5000u);
EXPECT_NEAR(state.GetProgress(), 50.0, 0.01);
// 获取需要续传的区间
auto missing = state.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
// 验证缺失区间
EXPECT_EQ(missing[0].offset, 4000u);
EXPECT_EQ(missing[0].length, 1000u);
EXPECT_EQ(missing[1].offset, 6000u);
EXPECT_EQ(missing[1].length, 4000u);
}
TEST_F(ResumeScenarioTest, ResumeAndComplete) {
FileRecvState state;
state.Initialize(12345, 0, 10000, "large_file.bin");
// 初始接收
state.AddChunk(0, 3000);
state.AddChunk(7000, 3000);
EXPECT_FALSE(state.IsComplete());
// 续传缺失部分
auto missing = state.GetMissingRanges();
for (const auto& r : missing) {
state.AddChunk(r.offset, r.length);
}
EXPECT_TRUE(state.IsComplete());
EXPECT_EQ(state.GetReceivedBytes(), 10000u);
}
TEST_F(ResumeScenarioTest, SmallChunksReassembly) {
FileRecvState state;
state.Initialize(1, 0, 1000, "file.txt");
// 模拟接收很多小块
for (uint64_t i = 0; i < 1000; i += 10) {
state.AddChunk(i, 10);
}
EXPECT_TRUE(state.IsComplete());
// 验证区间已合并
const auto& ranges = state.GetReceivedRanges();
EXPECT_EQ(ranges.size(), 1u);
EXPECT_EQ(ranges[0].offset, 0u);
EXPECT_EQ(ranges[0].length, 1000u);
}
// ============================================
// 大文件场景测试
// ============================================
class LargeFileScenarioTest : public ::testing::Test {};
TEST_F(LargeFileScenarioTest, FileGreaterThan4GB) {
FileRecvState state;
uint64_t fileSize = 5ULL * 1024 * 1024 * 1024; // 5 GB
state.Initialize(1, 0, fileSize, "huge.bin");
uint64_t chunkSize = 64 * 1024; // 64 KB chunks
// 添加几个大区间
state.AddChunk(0, 1ULL * 1024 * 1024 * 1024); // 1 GB
state.AddChunk(3ULL * 1024 * 1024 * 1024, 1ULL * 1024 * 1024 * 1024); // 1 GB at 3GB
EXPECT_EQ(state.GetReceivedBytes(), 2ULL * 1024 * 1024 * 1024);
auto missing = state.GetMissingRanges();
ASSERT_EQ(missing.size(), 2u);
// 1GB-3GB 缺失
EXPECT_EQ(missing[0].offset, 1ULL * 1024 * 1024 * 1024);
EXPECT_EQ(missing[0].length, 2ULL * 1024 * 1024 * 1024);
// 4GB-5GB 缺失
EXPECT_EQ(missing[1].offset, 4ULL * 1024 * 1024 * 1024);
EXPECT_EQ(missing[1].length, 1ULL * 1024 * 1024 * 1024);
}
// ============================================
// 边界条件测试
// ============================================
class ChunkBoundaryTest : public ::testing::Test {};
TEST_F(ChunkBoundaryTest, ZeroFileSize) {
FileRecvState state;
state.Initialize(1, 0, 0, "empty.txt");
EXPECT_FALSE(state.IsComplete()); // 0大小文件不算完成
EXPECT_TRUE(state.GetMissingRanges().empty());
}
TEST_F(ChunkBoundaryTest, SingleByteFile) {
FileRecvState state;
state.Initialize(1, 0, 1, "tiny.txt");
state.AddChunk(0, 1);
EXPECT_TRUE(state.IsComplete());
}
TEST_F(ChunkBoundaryTest, MaxValues) {
RangeManager rm(UINT64_MAX);
// 添加接近最大值的区间
rm.AddRange(UINT64_MAX - 1000, 500);
EXPECT_EQ(rm.GetReceivedBytes(), 500u);
}
TEST_F(ChunkBoundaryTest, OverlappingAtBoundary) {
RangeManager rm(1000);
rm.AddRange(0, 500);
rm.AddRange(499, 2); // 重叠1字节
EXPECT_EQ(rm.GetReceivedBytes(), 501u);
ASSERT_EQ(rm.GetRanges().size(), 1u);
}
// ============================================
// 性能相关测试
// ============================================
class ChunkPerformanceTest : public ::testing::Test {};
TEST_F(ChunkPerformanceTest, ManySmallRanges) {
RangeManager rm(1000000);
// 添加大量不连续的小区间
for (uint64_t i = 0; i < 1000000; i += 20) {
rm.AddRange(i, 10);
}
// 验证区间数量合理
EXPECT_LE(rm.GetRanges().size(), 50000u);
EXPECT_EQ(rm.GetReceivedBytes(), 500000u);
}
TEST_F(ChunkPerformanceTest, ManyContiguousRanges) {
RangeManager rm(1000000);
// 添加大量连续的小区间(应该全部合并)
for (uint64_t i = 0; i < 1000; ++i) {
rm.AddRange(i * 1000, 1000);
}
// 应该合并成单个区间
ASSERT_EQ(rm.GetRanges().size(), 1u);
EXPECT_EQ(rm.GetReceivedBytes(), 1000000u);
EXPECT_TRUE(rm.IsComplete());
}

View File

@@ -0,0 +1,700 @@
/**
* @file FileTransferV2Test.cpp
* @brief V2 文件传输逻辑测试
*
* 测试覆盖:
* - TransferOptionsV2 结构体
* - 传输ID生成
* - 包头构建与解析
* - 变长数据包处理
*/
#include <gtest/gtest.h>
#include <cstring>
#include <vector>
#include <map>
#include <random>
#include <chrono>
// ============================================
// 从 file_upload.h 复制的结构体定义(测试专用)
// ============================================
#pragma pack(push, 1)
enum FileFlagsV2 : uint16_t {
FFV2_NONE = 0x0000,
FFV2_LAST_CHUNK = 0x0001,
FFV2_RESUME_REQ = 0x0002,
FFV2_RESUME_RESP = 0x0004,
FFV2_CANCEL = 0x0008,
FFV2_DIRECTORY = 0x0010,
FFV2_COMPRESSED = 0x0020,
FFV2_ERROR = 0x0040,
};
struct FileChunkPacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint32_t totalFiles;
uint64_t fileSize;
uint64_t offset;
uint64_t dataLength;
uint64_t nameLength;
uint16_t flags;
uint16_t checksum;
uint8_t reserved[8];
};
struct FileRangeV2 {
uint64_t offset;
uint64_t length;
};
struct FileResumePacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint64_t receivedBytes;
uint16_t flags;
uint16_t rangeCount;
};
struct FileCompletePacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint8_t sha256[32];
};
struct FileQueryResumeV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileCount;
};
struct FileQueryResumeEntryV2 {
uint64_t fileSize;
uint16_t nameLength;
};
struct FileResumeResponseV2 {
uint8_t cmd;
uint64_t srcClientID;
uint64_t dstClientID;
uint16_t flags;
uint32_t fileCount;
};
struct FileResumeResponseEntryV2 {
uint32_t fileIndex;
uint64_t receivedBytes;
};
#pragma pack(pop)
// V2 传输选项
struct TransferOptionsV2 {
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
bool enableResume;
std::map<uint32_t, uint64_t> startOffsets;
TransferOptionsV2() : transferID(0), srcClientID(0), dstClientID(0), enableResume(true) {}
};
// ============================================
// 辅助函数(测试专用实现)
// ============================================
// 生成传输ID简化实现
uint64_t GenerateTransferID() {
static std::mt19937_64 rng(
static_cast<uint64_t>(std::chrono::high_resolution_clock::now().time_since_epoch().count())
);
return rng();
}
// 构建文件块包
std::vector<uint8_t> BuildFileChunkPacketV2(
uint64_t transferID,
uint64_t srcClientID,
uint64_t dstClientID,
uint32_t fileIndex,
uint32_t totalFiles,
uint64_t fileSize,
uint64_t offset,
const std::string& filename,
const std::vector<uint8_t>& data,
uint16_t flags = FFV2_NONE)
{
size_t totalSize = sizeof(FileChunkPacketV2) + filename.size() + data.size();
std::vector<uint8_t> buffer(totalSize);
FileChunkPacketV2* pkt = reinterpret_cast<FileChunkPacketV2*>(buffer.data());
pkt->cmd = 85; // COMMAND_SEND_FILE_V2
pkt->transferID = transferID;
pkt->srcClientID = srcClientID;
pkt->dstClientID = dstClientID;
pkt->fileIndex = fileIndex;
pkt->totalFiles = totalFiles;
pkt->fileSize = fileSize;
pkt->offset = offset;
pkt->dataLength = data.size();
pkt->nameLength = filename.size();
pkt->flags = flags;
pkt->checksum = 0;
memset(pkt->reserved, 0, sizeof(pkt->reserved));
// 追加文件名
memcpy(buffer.data() + sizeof(FileChunkPacketV2), filename.data(), filename.size());
// 追加数据
if (!data.empty()) {
memcpy(buffer.data() + sizeof(FileChunkPacketV2) + filename.size(), data.data(), data.size());
}
return buffer;
}
// 解析文件块包
bool ParseFileChunkPacketV2(
const uint8_t* buffer, size_t len,
FileChunkPacketV2& header,
std::string& filename,
std::vector<uint8_t>& data)
{
if (len < sizeof(FileChunkPacketV2)) {
return false;
}
memcpy(&header, buffer, sizeof(FileChunkPacketV2));
size_t expectedLen = sizeof(FileChunkPacketV2) + header.nameLength + header.dataLength;
if (len < expectedLen) {
return false;
}
const char* namePtr = reinterpret_cast<const char*>(buffer + sizeof(FileChunkPacketV2));
filename.assign(namePtr, header.nameLength);
if (header.dataLength > 0) {
const uint8_t* dataPtr = buffer + sizeof(FileChunkPacketV2) + header.nameLength;
data.assign(dataPtr, dataPtr + header.dataLength);
} else {
data.clear();
}
return true;
}
// 构建续传查询包
std::vector<uint8_t> BuildResumeQuery(
uint64_t transferID,
uint64_t srcClientID,
uint64_t dstClientID,
const std::vector<std::pair<std::string, uint64_t>>& files)
{
// 计算总大小
size_t totalSize = sizeof(FileQueryResumeV2);
for (const auto& file : files) {
totalSize += sizeof(FileQueryResumeEntryV2) + file.first.size();
}
std::vector<uint8_t> buffer(totalSize);
FileQueryResumeV2* pkt = reinterpret_cast<FileQueryResumeV2*>(buffer.data());
pkt->cmd = 88; // COMMAND_FILE_QUERY_RESUME
pkt->transferID = transferID;
pkt->srcClientID = srcClientID;
pkt->dstClientID = dstClientID;
pkt->fileCount = static_cast<uint32_t>(files.size());
uint8_t* ptr = buffer.data() + sizeof(FileQueryResumeV2);
for (const auto& file : files) {
FileQueryResumeEntryV2* entry = reinterpret_cast<FileQueryResumeEntryV2*>(ptr);
entry->fileSize = file.second;
entry->nameLength = static_cast<uint16_t>(file.first.size());
ptr += sizeof(FileQueryResumeEntryV2);
memcpy(ptr, file.first.data(), file.first.size());
ptr += file.first.size();
}
return buffer;
}
// 解析续传查询包
bool ParseResumeQuery(
const uint8_t* buffer, size_t len,
uint64_t& transferID,
uint64_t& srcClientID,
uint64_t& dstClientID,
std::vector<std::pair<std::string, uint64_t>>& files)
{
if (len < sizeof(FileQueryResumeV2)) {
return false;
}
const FileQueryResumeV2* pkt = reinterpret_cast<const FileQueryResumeV2*>(buffer);
transferID = pkt->transferID;
srcClientID = pkt->srcClientID;
dstClientID = pkt->dstClientID;
files.clear();
const uint8_t* ptr = buffer + sizeof(FileQueryResumeV2);
const uint8_t* end = buffer + len;
for (uint32_t i = 0; i < pkt->fileCount; ++i) {
if (ptr + sizeof(FileQueryResumeEntryV2) > end) {
return false;
}
const FileQueryResumeEntryV2* entry = reinterpret_cast<const FileQueryResumeEntryV2*>(ptr);
ptr += sizeof(FileQueryResumeEntryV2);
if (ptr + entry->nameLength > end) {
return false;
}
std::string name(reinterpret_cast<const char*>(ptr), entry->nameLength);
ptr += entry->nameLength;
files.emplace_back(name, entry->fileSize);
}
return true;
}
// ============================================
// TransferOptionsV2 测试
// ============================================
class TransferOptionsV2Test : public ::testing::Test {};
TEST_F(TransferOptionsV2Test, DefaultValues) {
TransferOptionsV2 options;
EXPECT_EQ(options.transferID, 0u);
EXPECT_EQ(options.srcClientID, 0u);
EXPECT_EQ(options.dstClientID, 0u);
EXPECT_TRUE(options.enableResume);
EXPECT_TRUE(options.startOffsets.empty());
}
TEST_F(TransferOptionsV2Test, SetStartOffsets) {
TransferOptionsV2 options;
options.startOffsets[0] = 1024;
options.startOffsets[1] = 2048;
options.startOffsets[2] = 0;
EXPECT_EQ(options.startOffsets.size(), 3u);
EXPECT_EQ(options.startOffsets[0], 1024u);
EXPECT_EQ(options.startOffsets[1], 2048u);
EXPECT_EQ(options.startOffsets[2], 0u);
}
TEST_F(TransferOptionsV2Test, C2CConfiguration) {
TransferOptionsV2 options;
options.transferID = 12345;
options.srcClientID = 100;
options.dstClientID = 200;
EXPECT_EQ(options.transferID, 12345u);
EXPECT_EQ(options.srcClientID, 100u);
EXPECT_EQ(options.dstClientID, 200u);
}
// ============================================
// TransferID 生成测试
// ============================================
class TransferIDTest : public ::testing::Test {};
TEST_F(TransferIDTest, GeneratesNonZero) {
// 多次生成应该都不为 0极低概率
for (int i = 0; i < 100; ++i) {
uint64_t id = GenerateTransferID();
// 不做强制断言,因为理论上可能为 0
// 但实际概率极低
if (id == 0) {
// 如果真的生成了 0再生成一次
id = GenerateTransferID();
}
}
SUCCEED();
}
TEST_F(TransferIDTest, GeneratesUnique) {
std::vector<uint64_t> ids;
for (int i = 0; i < 1000; ++i) {
ids.push_back(GenerateTransferID());
}
// 检查没有重复
std::sort(ids.begin(), ids.end());
auto it = std::unique(ids.begin(), ids.end());
EXPECT_EQ(it, ids.end()) << "Found duplicate transfer IDs";
}
// ============================================
// FileChunkPacketV2 构建测试
// ============================================
class FileChunkPacketV2BuildTest : public ::testing::Test {};
TEST_F(FileChunkPacketV2BuildTest, BuildSimplePacket) {
uint64_t transferID = 12345;
std::string filename = "test.txt";
std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0x04};
auto buffer = BuildFileChunkPacketV2(
transferID, 0, 0, 0, 1, 100, 0, filename, data);
EXPECT_EQ(buffer.size(), sizeof(FileChunkPacketV2) + filename.size() + data.size());
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->cmd, 85);
EXPECT_EQ(pkt->transferID, transferID);
EXPECT_EQ(pkt->nameLength, filename.size());
EXPECT_EQ(pkt->dataLength, data.size());
}
TEST_F(FileChunkPacketV2BuildTest, BuildWithFlags) {
auto buffer = BuildFileChunkPacketV2(
1, 0, 0, 0, 1, 100, 50, "file.bin", {}, FFV2_LAST_CHUNK);
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->flags, FFV2_LAST_CHUNK);
EXPECT_EQ(pkt->offset, 50u);
}
TEST_F(FileChunkPacketV2BuildTest, BuildC2CPacket) {
auto buffer = BuildFileChunkPacketV2(
999, 100, 200, 0, 3, 1024, 0, "shared/doc.pdf", {});
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->srcClientID, 100u);
EXPECT_EQ(pkt->dstClientID, 200u);
EXPECT_EQ(pkt->totalFiles, 3u);
}
TEST_F(FileChunkPacketV2BuildTest, BuildEmptyDataPacket) {
std::vector<uint8_t> emptyData;
auto buffer = BuildFileChunkPacketV2(
1, 0, 0, 0, 1, 0, 0, "empty.txt", emptyData);
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->dataLength, 0u);
EXPECT_EQ(pkt->fileSize, 0u);
}
TEST_F(FileChunkPacketV2BuildTest, BuildDirectoryPacket) {
auto buffer = BuildFileChunkPacketV2(
1, 0, 0, 0, 1, 0, 0, "subdir/", {}, FFV2_DIRECTORY);
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->flags & FFV2_DIRECTORY, FFV2_DIRECTORY);
EXPECT_EQ(pkt->dataLength, 0u);
}
// ============================================
// FileChunkPacketV2 解析测试
// ============================================
class FileChunkPacketV2ParseTest : public ::testing::Test {};
TEST_F(FileChunkPacketV2ParseTest, ParseValidPacket) {
std::string originalName = "test/file.txt";
std::vector<uint8_t> originalData = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
auto buffer = BuildFileChunkPacketV2(
12345, 100, 200, 3, 10, 1024 * 1024, 512, originalName, originalData, FFV2_COMPRESSED);
FileChunkPacketV2 header;
std::string parsedName;
std::vector<uint8_t> parsedData;
bool result = ParseFileChunkPacketV2(buffer.data(), buffer.size(), header, parsedName, parsedData);
EXPECT_TRUE(result);
EXPECT_EQ(header.transferID, 12345u);
EXPECT_EQ(header.srcClientID, 100u);
EXPECT_EQ(header.dstClientID, 200u);
EXPECT_EQ(header.fileIndex, 3u);
EXPECT_EQ(header.totalFiles, 10u);
EXPECT_EQ(header.fileSize, 1024u * 1024u);
EXPECT_EQ(header.offset, 512u);
EXPECT_EQ(header.flags, FFV2_COMPRESSED);
EXPECT_EQ(parsedName, originalName);
EXPECT_EQ(parsedData, originalData);
}
TEST_F(FileChunkPacketV2ParseTest, ParseTruncatedHeader) {
std::vector<uint8_t> truncated(sizeof(FileChunkPacketV2) - 10);
FileChunkPacketV2 header;
std::string name;
std::vector<uint8_t> data;
bool result = ParseFileChunkPacketV2(truncated.data(), truncated.size(), header, name, data);
EXPECT_FALSE(result);
}
TEST_F(FileChunkPacketV2ParseTest, ParseTruncatedData) {
auto buffer = BuildFileChunkPacketV2(
1, 0, 0, 0, 1, 100, 0, "file.txt", {0x01, 0x02, 0x03});
// 截断数据部分
std::vector<uint8_t> truncated(buffer.begin(), buffer.end() - 2);
FileChunkPacketV2 header;
std::string name;
std::vector<uint8_t> data;
bool result = ParseFileChunkPacketV2(truncated.data(), truncated.size(), header, name, data);
EXPECT_FALSE(result);
}
TEST_F(FileChunkPacketV2ParseTest, RoundTrip) {
// 构建各种类型的包并验证往返
struct TestCase {
uint64_t transferID;
uint32_t fileIndex;
uint64_t fileSize;
uint64_t offset;
std::string filename;
std::vector<uint8_t> data;
uint16_t flags;
};
std::vector<TestCase> cases = {
{1, 0, 100, 0, "a.txt", {0x01}, FFV2_NONE},
{UINT64_MAX, 999, UINT64_MAX, UINT64_MAX - 100, "path/to/file.bin", {}, FFV2_LAST_CHUNK},
{12345, 5, 1024, 512, "中文文件.txt", {0xAA, 0xBB, 0xCC}, FFV2_COMPRESSED | FFV2_LAST_CHUNK},
};
for (const auto& tc : cases) {
auto buffer = BuildFileChunkPacketV2(
tc.transferID, 0, 0, tc.fileIndex, 10, tc.fileSize, tc.offset, tc.filename, tc.data, tc.flags);
FileChunkPacketV2 header;
std::string name;
std::vector<uint8_t> data;
ASSERT_TRUE(ParseFileChunkPacketV2(buffer.data(), buffer.size(), header, name, data));
EXPECT_EQ(header.transferID, tc.transferID);
EXPECT_EQ(header.fileIndex, tc.fileIndex);
EXPECT_EQ(header.fileSize, tc.fileSize);
EXPECT_EQ(header.offset, tc.offset);
EXPECT_EQ(header.flags, tc.flags);
EXPECT_EQ(name, tc.filename);
EXPECT_EQ(data, tc.data);
}
}
// ============================================
// 续传查询包测试
// ============================================
class ResumeQueryTest : public ::testing::Test {};
TEST_F(ResumeQueryTest, BuildEmpty) {
std::vector<std::pair<std::string, uint64_t>> files;
auto buffer = BuildResumeQuery(12345, 100, 200, files);
EXPECT_EQ(buffer.size(), sizeof(FileQueryResumeV2));
const FileQueryResumeV2* pkt = reinterpret_cast<const FileQueryResumeV2*>(buffer.data());
EXPECT_EQ(pkt->cmd, 88);
EXPECT_EQ(pkt->fileCount, 0u);
}
TEST_F(ResumeQueryTest, BuildSingleFile) {
std::vector<std::pair<std::string, uint64_t>> files = {
{"file.txt", 1024}
};
auto buffer = BuildResumeQuery(12345, 100, 200, files);
const FileQueryResumeV2* pkt = reinterpret_cast<const FileQueryResumeV2*>(buffer.data());
EXPECT_EQ(pkt->fileCount, 1u);
}
TEST_F(ResumeQueryTest, BuildMultipleFiles) {
std::vector<std::pair<std::string, uint64_t>> files = {
{"file1.txt", 1024},
{"dir/file2.bin", 2048},
{"another/path/file3.dat", 4096}
};
auto buffer = BuildResumeQuery(12345, 100, 200, files);
const FileQueryResumeV2* pkt = reinterpret_cast<const FileQueryResumeV2*>(buffer.data());
EXPECT_EQ(pkt->fileCount, 3u);
}
TEST_F(ResumeQueryTest, ParseRoundTrip) {
std::vector<std::pair<std::string, uint64_t>> original = {
{"file1.txt", 1024},
{"subdir/file2.bin", UINT64_MAX},
{"中文/文件.txt", 0}
};
auto buffer = BuildResumeQuery(99999, 111, 222, original);
uint64_t transferID, srcClientID, dstClientID;
std::vector<std::pair<std::string, uint64_t>> parsed;
bool result = ParseResumeQuery(buffer.data(), buffer.size(), transferID, srcClientID, dstClientID, parsed);
EXPECT_TRUE(result);
EXPECT_EQ(transferID, 99999u);
EXPECT_EQ(srcClientID, 111u);
EXPECT_EQ(dstClientID, 222u);
ASSERT_EQ(parsed.size(), original.size());
for (size_t i = 0; i < original.size(); ++i) {
EXPECT_EQ(parsed[i].first, original[i].first);
EXPECT_EQ(parsed[i].second, original[i].second);
}
}
TEST_F(ResumeQueryTest, ParseTruncated) {
std::vector<std::pair<std::string, uint64_t>> files = {
{"file.txt", 1024}
};
auto buffer = BuildResumeQuery(1, 0, 0, files);
// 截断
std::vector<uint8_t> truncated(buffer.begin(), buffer.begin() + sizeof(FileQueryResumeV2) + 5);
uint64_t transferID, srcClientID, dstClientID;
std::vector<std::pair<std::string, uint64_t>> parsed;
bool result = ParseResumeQuery(truncated.data(), truncated.size(), transferID, srcClientID, dstClientID, parsed);
EXPECT_FALSE(result);
}
// ============================================
// 大文件支持测试
// ============================================
class LargeFileTest : public ::testing::Test {};
TEST_F(LargeFileTest, FileSize_GreaterThan4GB) {
uint64_t largeSize = 5ULL * 1024 * 1024 * 1024; // 5 GB
auto buffer = BuildFileChunkPacketV2(
1, 0, 0, 0, 1, largeSize, 0, "large.bin", {});
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->fileSize, largeSize);
}
TEST_F(LargeFileTest, Offset_GreaterThan4GB) {
uint64_t largeOffset = 10ULL * 1024 * 1024 * 1024; // 10 GB
uint64_t fileSize = 20ULL * 1024 * 1024 * 1024; // 20 GB
auto buffer = BuildFileChunkPacketV2(
1, 0, 0, 0, 1, fileSize, largeOffset, "huge.bin", {});
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->fileSize, fileSize);
EXPECT_EQ(pkt->offset, largeOffset);
}
TEST_F(LargeFileTest, MaxValues) {
auto buffer = BuildFileChunkPacketV2(
UINT64_MAX, UINT64_MAX, UINT64_MAX,
UINT32_MAX, UINT32_MAX, UINT64_MAX, UINT64_MAX,
"max.bin", {}, UINT16_MAX);
FileChunkPacketV2 header;
std::string name;
std::vector<uint8_t> data;
ASSERT_TRUE(ParseFileChunkPacketV2(buffer.data(), buffer.size(), header, name, data));
EXPECT_EQ(header.transferID, UINT64_MAX);
EXPECT_EQ(header.srcClientID, UINT64_MAX);
EXPECT_EQ(header.dstClientID, UINT64_MAX);
EXPECT_EQ(header.fileIndex, UINT32_MAX);
EXPECT_EQ(header.totalFiles, UINT32_MAX);
EXPECT_EQ(header.fileSize, UINT64_MAX);
EXPECT_EQ(header.offset, UINT64_MAX);
}
// ============================================
// 多文件传输测试
// ============================================
class MultiFileTransferTest : public ::testing::Test {};
TEST_F(MultiFileTransferTest, SequentialFileIndices) {
std::vector<std::pair<std::string, uint64_t>> files = {
{"file1.txt", 100},
{"file2.txt", 200},
{"file3.txt", 300}
};
for (uint32_t i = 0; i < files.size(); ++i) {
auto buffer = BuildFileChunkPacketV2(
12345, 0, 0, i, static_cast<uint32_t>(files.size()),
files[i].second, 0, files[i].first, {}, FFV2_LAST_CHUNK);
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->fileIndex, i);
EXPECT_EQ(pkt->totalFiles, files.size());
}
}
TEST_F(MultiFileTransferTest, ConsistentTransferID) {
uint64_t transferID = GenerateTransferID();
for (int i = 0; i < 5; ++i) {
auto buffer = BuildFileChunkPacketV2(
transferID, 0, 0, i, 5, 1000 * (i + 1), 0, "file" + std::to_string(i), {});
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->transferID, transferID);
}
}
// ============================================
// 错误处理测试
// ============================================
class ErrorHandlingTest : public ::testing::Test {};
TEST_F(ErrorHandlingTest, CancelFlag) {
auto buffer = BuildFileChunkPacketV2(
12345, 0, 0, 0, 1, 0, 0, "", {}, FFV2_CANCEL);
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->flags & FFV2_CANCEL, FFV2_CANCEL);
}
TEST_F(ErrorHandlingTest, ErrorFlag) {
auto buffer = BuildFileChunkPacketV2(
12345, 0, 0, 0, 1, 0, 0, "", {}, FFV2_ERROR);
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_EQ(pkt->flags & FFV2_ERROR, FFV2_ERROR);
}
TEST_F(ErrorHandlingTest, CombinedErrorFlags) {
uint16_t flags = FFV2_ERROR | FFV2_CANCEL | FFV2_LAST_CHUNK;
auto buffer = BuildFileChunkPacketV2(
12345, 0, 0, 0, 1, 0, 0, "", {}, flags);
const FileChunkPacketV2* pkt = reinterpret_cast<const FileChunkPacketV2*>(buffer.data());
EXPECT_TRUE(pkt->flags & FFV2_ERROR);
EXPECT_TRUE(pkt->flags & FFV2_CANCEL);
EXPECT_TRUE(pkt->flags & FFV2_LAST_CHUNK);
}

View File

@@ -0,0 +1,778 @@
/**
* @file ResumeStateTest.cpp
* @brief 断点续传状态管理测试
*
* 测试覆盖:
* - 续传状态序列化/反序列化
* - 续传请求/响应包构建
* - 状态文件格式
* - 多文件续传管理
*/
#include <gtest/gtest.h>
#include <cstring>
#include <vector>
#include <map>
#include <string>
#include <cstdint>
// ============================================
// 协议结构(测试专用)
// ============================================
#pragma pack(push, 1)
struct FileRangeV2 {
uint64_t offset;
uint64_t length;
};
struct FileResumePacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint64_t receivedBytes;
uint16_t flags;
uint16_t rangeCount;
};
enum FileFlagsV2 : uint16_t {
FFV2_NONE = 0x0000,
FFV2_RESUME_REQ = 0x0002,
FFV2_RESUME_RESP = 0x0004,
};
struct FileResumeResponseV2 {
uint8_t cmd;
uint64_t srcClientID;
uint64_t dstClientID;
uint16_t flags;
uint32_t fileCount;
};
struct FileResumeResponseEntryV2 {
uint32_t fileIndex;
uint64_t receivedBytes;
};
#pragma pack(pop)
// ============================================
// 续传状态管理类(测试专用实现)
// ============================================
struct FileResumeEntry {
uint32_t fileIndex;
uint64_t fileSize;
uint64_t receivedBytes;
std::string relativePath;
std::vector<std::pair<uint64_t, uint64_t>> receivedRanges;
};
class ResumeStateManager {
public:
ResumeStateManager() : m_transferID(0), m_srcClientID(0), m_dstClientID(0) {}
void Initialize(uint64_t transferID, uint64_t srcClientID, uint64_t dstClientID,
const std::string& targetDir) {
m_transferID = transferID;
m_srcClientID = srcClientID;
m_dstClientID = dstClientID;
m_targetDir = targetDir;
m_entries.clear();
}
void AddFile(uint32_t fileIndex, uint64_t fileSize, const std::string& path) {
FileResumeEntry entry;
entry.fileIndex = fileIndex;
entry.fileSize = fileSize;
entry.receivedBytes = 0;
entry.relativePath = path;
m_entries.push_back(entry);
}
void UpdateProgress(uint32_t fileIndex, uint64_t offset, uint64_t length) {
for (auto& entry : m_entries) {
if (entry.fileIndex == fileIndex) {
entry.receivedRanges.emplace_back(offset, length);
entry.receivedBytes += length;
break;
}
}
}
bool GetFileState(uint32_t fileIndex, FileResumeEntry& outEntry) const {
for (const auto& entry : m_entries) {
if (entry.fileIndex == fileIndex) {
outEntry = entry;
return true;
}
}
return false;
}
// 序列化为字节流
std::vector<uint8_t> Serialize() const {
std::vector<uint8_t> buffer;
// Header
auto appendU64 = [&buffer](uint64_t val) {
for (int i = 0; i < 8; ++i) {
buffer.push_back(static_cast<uint8_t>(val >> (i * 8)));
}
};
auto appendU32 = [&buffer](uint32_t val) {
for (int i = 0; i < 4; ++i) {
buffer.push_back(static_cast<uint8_t>(val >> (i * 8)));
}
};
auto appendU16 = [&buffer](uint16_t val) {
buffer.push_back(static_cast<uint8_t>(val & 0xFF));
buffer.push_back(static_cast<uint8_t>(val >> 8));
};
auto appendString = [&buffer, &appendU16](const std::string& str) {
appendU16(static_cast<uint16_t>(str.size()));
buffer.insert(buffer.end(), str.begin(), str.end());
};
// Magic
buffer.push_back('R');
buffer.push_back('S');
buffer.push_back('T');
buffer.push_back('V'); // Resume State V2
appendU64(m_transferID);
appendU64(m_srcClientID);
appendU64(m_dstClientID);
appendString(m_targetDir);
appendU32(static_cast<uint32_t>(m_entries.size()));
for (const auto& entry : m_entries) {
appendU32(entry.fileIndex);
appendU64(entry.fileSize);
appendU64(entry.receivedBytes);
appendString(entry.relativePath);
appendU16(static_cast<uint16_t>(entry.receivedRanges.size()));
for (const auto& range : entry.receivedRanges) {
appendU64(range.first);
appendU64(range.second);
}
}
return buffer;
}
// 从字节流反序列化
bool Deserialize(const std::vector<uint8_t>& buffer) {
if (buffer.size() < 8) return false;
size_t pos = 0;
auto readU64 = [&buffer, &pos]() -> uint64_t {
if (pos + 8 > buffer.size()) return 0;
uint64_t val = 0;
for (int i = 0; i < 8; ++i) {
val |= static_cast<uint64_t>(buffer[pos++]) << (i * 8);
}
return val;
};
auto readU32 = [&buffer, &pos]() -> uint32_t {
if (pos + 4 > buffer.size()) return 0;
uint32_t val = 0;
for (int i = 0; i < 4; ++i) {
val |= static_cast<uint32_t>(buffer[pos++]) << (i * 8);
}
return val;
};
auto readU16 = [&buffer, &pos]() -> uint16_t {
if (pos + 2 > buffer.size()) return 0;
uint16_t val = buffer[pos] | (buffer[pos + 1] << 8);
pos += 2;
return val;
};
auto readString = [&buffer, &pos, &readU16]() -> std::string {
uint16_t len = readU16();
if (pos + len > buffer.size()) return "";
std::string str(buffer.begin() + pos, buffer.begin() + pos + len);
pos += len;
return str;
};
// Check magic
if (buffer[0] != 'R' || buffer[1] != 'S' || buffer[2] != 'T' || buffer[3] != 'V') {
return false;
}
pos = 4;
m_transferID = readU64();
m_srcClientID = readU64();
m_dstClientID = readU64();
m_targetDir = readString();
uint32_t entryCount = readU32();
m_entries.clear();
for (uint32_t i = 0; i < entryCount; ++i) {
FileResumeEntry entry;
entry.fileIndex = readU32();
entry.fileSize = readU64();
entry.receivedBytes = readU64();
entry.relativePath = readString();
uint16_t rangeCount = readU16();
for (uint16_t j = 0; j < rangeCount; ++j) {
uint64_t offset = readU64();
uint64_t length = readU64();
entry.receivedRanges.emplace_back(offset, length);
}
m_entries.push_back(entry);
}
return true;
}
uint64_t GetTransferID() const { return m_transferID; }
uint64_t GetSrcClientID() const { return m_srcClientID; }
uint64_t GetDstClientID() const { return m_dstClientID; }
const std::string& GetTargetDir() const { return m_targetDir; }
size_t GetFileCount() const { return m_entries.size(); }
// 获取所有文件的接收偏移映射
std::map<uint32_t, uint64_t> GetReceivedOffsets() const {
std::map<uint32_t, uint64_t> offsets;
for (const auto& entry : m_entries) {
offsets[entry.fileIndex] = entry.receivedBytes;
}
return offsets;
}
private:
uint64_t m_transferID;
uint64_t m_srcClientID;
uint64_t m_dstClientID;
std::string m_targetDir;
std::vector<FileResumeEntry> m_entries;
};
// ============================================
// 续传包构建/解析辅助函数
// ============================================
std::vector<uint8_t> BuildResumeRequest(
uint64_t transferID,
uint64_t srcClientID,
uint64_t dstClientID,
uint32_t fileIndex,
uint64_t fileSize,
uint64_t receivedBytes,
const std::vector<std::pair<uint64_t, uint64_t>>& ranges)
{
size_t size = sizeof(FileResumePacketV2) + ranges.size() * sizeof(FileRangeV2);
std::vector<uint8_t> buffer(size);
FileResumePacketV2* pkt = reinterpret_cast<FileResumePacketV2*>(buffer.data());
pkt->cmd = 86; // COMMAND_FILE_RESUME
pkt->transferID = transferID;
pkt->srcClientID = srcClientID;
pkt->dstClientID = dstClientID;
pkt->fileIndex = fileIndex;
pkt->fileSize = fileSize;
pkt->receivedBytes = receivedBytes;
pkt->flags = FFV2_RESUME_REQ;
pkt->rangeCount = static_cast<uint16_t>(ranges.size());
FileRangeV2* rangePtr = reinterpret_cast<FileRangeV2*>(buffer.data() + sizeof(FileResumePacketV2));
for (size_t i = 0; i < ranges.size(); ++i) {
rangePtr[i].offset = ranges[i].first;
rangePtr[i].length = ranges[i].second;
}
return buffer;
}
bool ParseResumeRequest(
const uint8_t* buffer, size_t len,
FileResumePacketV2& header,
std::vector<std::pair<uint64_t, uint64_t>>& ranges)
{
if (len < sizeof(FileResumePacketV2)) {
return false;
}
memcpy(&header, buffer, sizeof(FileResumePacketV2));
size_t expectedLen = sizeof(FileResumePacketV2) + header.rangeCount * sizeof(FileRangeV2);
if (len < expectedLen) {
return false;
}
ranges.clear();
const FileRangeV2* rangePtr = reinterpret_cast<const FileRangeV2*>(buffer + sizeof(FileResumePacketV2));
for (uint16_t i = 0; i < header.rangeCount; ++i) {
ranges.emplace_back(rangePtr[i].offset, rangePtr[i].length);
}
return true;
}
std::vector<uint8_t> BuildResumeResponse(
uint64_t srcClientID,
uint64_t dstClientID,
const std::map<uint32_t, uint64_t>& offsets)
{
size_t size = sizeof(FileResumeResponseV2) + offsets.size() * sizeof(FileResumeResponseEntryV2);
std::vector<uint8_t> buffer(size);
FileResumeResponseV2* pkt = reinterpret_cast<FileResumeResponseV2*>(buffer.data());
pkt->cmd = 86; // COMMAND_FILE_RESUME
pkt->srcClientID = srcClientID;
pkt->dstClientID = dstClientID;
pkt->flags = FFV2_RESUME_RESP;
pkt->fileCount = static_cast<uint32_t>(offsets.size());
FileResumeResponseEntryV2* entryPtr = reinterpret_cast<FileResumeResponseEntryV2*>(
buffer.data() + sizeof(FileResumeResponseV2));
size_t i = 0;
for (const auto& kv : offsets) {
entryPtr[i].fileIndex = kv.first;
entryPtr[i].receivedBytes = kv.second;
++i;
}
return buffer;
}
bool ParseResumeResponse(
const uint8_t* buffer, size_t len,
std::map<uint32_t, uint64_t>& offsets)
{
if (len < sizeof(FileResumeResponseV2)) {
return false;
}
const FileResumeResponseV2* pkt = reinterpret_cast<const FileResumeResponseV2*>(buffer);
if ((pkt->flags & FFV2_RESUME_RESP) == 0) {
return false;
}
size_t expectedLen = sizeof(FileResumeResponseV2) + pkt->fileCount * sizeof(FileResumeResponseEntryV2);
if (len < expectedLen) {
return false;
}
offsets.clear();
const FileResumeResponseEntryV2* entryPtr = reinterpret_cast<const FileResumeResponseEntryV2*>(
buffer + sizeof(FileResumeResponseV2));
for (uint32_t i = 0; i < pkt->fileCount; ++i) {
offsets[entryPtr[i].fileIndex] = entryPtr[i].receivedBytes;
}
return true;
}
// ============================================
// ResumeStateManager 测试
// ============================================
class ResumeStateManagerTest : public ::testing::Test {};
TEST_F(ResumeStateManagerTest, Initialize) {
ResumeStateManager mgr;
mgr.Initialize(12345, 100, 200, "C:\\Downloads\\");
EXPECT_EQ(mgr.GetTransferID(), 12345u);
EXPECT_EQ(mgr.GetSrcClientID(), 100u);
EXPECT_EQ(mgr.GetDstClientID(), 200u);
EXPECT_EQ(mgr.GetTargetDir(), "C:\\Downloads\\");
EXPECT_EQ(mgr.GetFileCount(), 0u);
}
TEST_F(ResumeStateManagerTest, AddFiles) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 1000, "file1.txt");
mgr.AddFile(1, 2000, "subdir/file2.bin");
mgr.AddFile(2, 3000, "another/path/file3.dat");
EXPECT_EQ(mgr.GetFileCount(), 3u);
FileResumeEntry entry;
ASSERT_TRUE(mgr.GetFileState(1, entry));
EXPECT_EQ(entry.fileSize, 2000u);
EXPECT_EQ(entry.relativePath, "subdir/file2.bin");
}
TEST_F(ResumeStateManagerTest, UpdateProgress) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 10000, "file.bin");
mgr.UpdateProgress(0, 0, 2000);
mgr.UpdateProgress(0, 2000, 3000);
FileResumeEntry entry;
ASSERT_TRUE(mgr.GetFileState(0, entry));
EXPECT_EQ(entry.receivedBytes, 5000u);
EXPECT_EQ(entry.receivedRanges.size(), 2u);
}
TEST_F(ResumeStateManagerTest, GetReceivedOffsets) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 1000, "a.txt");
mgr.AddFile(1, 2000, "b.txt");
mgr.AddFile(2, 3000, "c.txt");
mgr.UpdateProgress(0, 0, 500);
mgr.UpdateProgress(1, 0, 1500);
mgr.UpdateProgress(2, 0, 2500);
auto offsets = mgr.GetReceivedOffsets();
EXPECT_EQ(offsets.size(), 3u);
EXPECT_EQ(offsets[0], 500u);
EXPECT_EQ(offsets[1], 1500u);
EXPECT_EQ(offsets[2], 2500u);
}
// ============================================
// 序列化/反序列化测试
// ============================================
class ResumeSerializationTest : public ::testing::Test {};
TEST_F(ResumeSerializationTest, EmptyState) {
ResumeStateManager mgr1, mgr2;
mgr1.Initialize(12345, 100, 200, "C:\\Target\\");
auto buffer = mgr1.Serialize();
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetTransferID(), 12345u);
EXPECT_EQ(mgr2.GetSrcClientID(), 100u);
EXPECT_EQ(mgr2.GetDstClientID(), 200u);
EXPECT_EQ(mgr2.GetTargetDir(), "C:\\Target\\");
EXPECT_EQ(mgr2.GetFileCount(), 0u);
}
TEST_F(ResumeSerializationTest, SingleFile) {
ResumeStateManager mgr1, mgr2;
mgr1.Initialize(1, 0, 0, "/tmp/download/");
mgr1.AddFile(0, 10000, "test.bin");
mgr1.UpdateProgress(0, 0, 5000);
auto buffer = mgr1.Serialize();
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetFileCount(), 1u);
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.fileSize, 10000u);
EXPECT_EQ(entry.receivedBytes, 5000u);
EXPECT_EQ(entry.relativePath, "test.bin");
}
TEST_F(ResumeSerializationTest, MultipleFiles) {
ResumeStateManager mgr1, mgr2;
mgr1.Initialize(99999, 111, 222, "D:\\Backup\\");
mgr1.AddFile(0, 1000, "file1.txt");
mgr1.AddFile(1, 2000, "dir/file2.bin");
mgr1.AddFile(2, 3000, "path/to/file3.dat");
mgr1.UpdateProgress(0, 0, 1000); // 完成
mgr1.UpdateProgress(1, 0, 500);
mgr1.UpdateProgress(1, 500, 500);
mgr1.UpdateProgress(2, 0, 1000);
mgr1.UpdateProgress(2, 2000, 500);
auto buffer = mgr1.Serialize();
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetFileCount(), 3u);
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.receivedBytes, 1000u);
ASSERT_TRUE(mgr2.GetFileState(1, entry));
EXPECT_EQ(entry.receivedBytes, 1000u);
EXPECT_EQ(entry.receivedRanges.size(), 2u);
ASSERT_TRUE(mgr2.GetFileState(2, entry));
EXPECT_EQ(entry.receivedBytes, 1500u);
EXPECT_EQ(entry.receivedRanges.size(), 2u);
}
TEST_F(ResumeSerializationTest, InvalidMagic) {
std::vector<uint8_t> invalidBuffer = {'X', 'X', 'X', 'X', 0, 0, 0, 0};
ResumeStateManager mgr;
EXPECT_FALSE(mgr.Deserialize(invalidBuffer));
}
TEST_F(ResumeSerializationTest, TruncatedBuffer) {
ResumeStateManager mgr1;
mgr1.Initialize(1, 0, 0, "test");
mgr1.AddFile(0, 1000, "file.txt");
auto buffer = mgr1.Serialize();
// 截断
std::vector<uint8_t> truncated(buffer.begin(), buffer.begin() + 10);
ResumeStateManager mgr2;
// 可能成功也可能失败,取决于截断位置
// 主要验证不会崩溃
mgr2.Deserialize(truncated);
SUCCEED();
}
// ============================================
// 续传请求/响应包测试
// ============================================
class ResumePacketTest : public ::testing::Test {};
TEST_F(ResumePacketTest, BuildResumeRequest_NoRanges) {
std::vector<std::pair<uint64_t, uint64_t>> ranges;
auto buffer = BuildResumeRequest(12345, 100, 200, 5, 10000, 0, ranges);
EXPECT_EQ(buffer.size(), sizeof(FileResumePacketV2));
const FileResumePacketV2* pkt = reinterpret_cast<const FileResumePacketV2*>(buffer.data());
EXPECT_EQ(pkt->cmd, 86);
EXPECT_EQ(pkt->flags, FFV2_RESUME_REQ);
EXPECT_EQ(pkt->rangeCount, 0);
}
TEST_F(ResumePacketTest, BuildResumeRequest_WithRanges) {
std::vector<std::pair<uint64_t, uint64_t>> ranges = {
{0, 1000},
{2000, 500},
{5000, 2000}
};
auto buffer = BuildResumeRequest(1, 0, 0, 0, 10000, 3500, ranges);
FileResumePacketV2 header;
std::vector<std::pair<uint64_t, uint64_t>> parsedRanges;
ASSERT_TRUE(ParseResumeRequest(buffer.data(), buffer.size(), header, parsedRanges));
EXPECT_EQ(header.fileSize, 10000u);
EXPECT_EQ(header.receivedBytes, 3500u);
ASSERT_EQ(parsedRanges.size(), 3u);
EXPECT_EQ(parsedRanges[0].first, 0u);
EXPECT_EQ(parsedRanges[0].second, 1000u);
EXPECT_EQ(parsedRanges[2].first, 5000u);
}
TEST_F(ResumePacketTest, BuildResumeResponse) {
std::map<uint32_t, uint64_t> offsets = {
{0, 1000},
{1, 0},
{2, 5000}
};
auto buffer = BuildResumeResponse(100, 200, offsets);
std::map<uint32_t, uint64_t> parsedOffsets;
ASSERT_TRUE(ParseResumeResponse(buffer.data(), buffer.size(), parsedOffsets));
EXPECT_EQ(parsedOffsets.size(), 3u);
EXPECT_EQ(parsedOffsets[0], 1000u);
EXPECT_EQ(parsedOffsets[1], 0u);
EXPECT_EQ(parsedOffsets[2], 5000u);
}
TEST_F(ResumePacketTest, ParseTruncatedRequest) {
auto buffer = BuildResumeRequest(1, 0, 0, 0, 1000, 0, {{0, 500}});
// 截断
FileResumePacketV2 header;
std::vector<std::pair<uint64_t, uint64_t>> ranges;
EXPECT_FALSE(ParseResumeRequest(buffer.data(), sizeof(FileResumePacketV2) - 5, header, ranges));
}
TEST_F(ResumePacketTest, ParseTruncatedResponse) {
std::map<uint32_t, uint64_t> offsets = {{0, 1000}};
auto buffer = BuildResumeResponse(0, 0, offsets);
// 截断
std::map<uint32_t, uint64_t> parsedOffsets;
EXPECT_FALSE(ParseResumeResponse(buffer.data(), sizeof(FileResumeResponseV2) - 5, parsedOffsets));
}
// ============================================
// 续传场景测试
// ============================================
class ResumeScenarioTest : public ::testing::Test {};
TEST_F(ResumeScenarioTest, SimulateInterruptAndResume) {
// 第一次传输,接收了部分数据
ResumeStateManager session1;
session1.Initialize(12345, 100, 0, "C:\\Downloads\\");
session1.AddFile(0, 10000, "large_file.bin");
session1.AddFile(1, 5000, "small_file.txt");
session1.UpdateProgress(0, 0, 3000); // 30%
session1.UpdateProgress(1, 0, 5000); // 100%
// 保存状态
auto savedState = session1.Serialize();
// 模拟程序重启,恢复状态
ResumeStateManager session2;
ASSERT_TRUE(session2.Deserialize(savedState));
// 获取续传偏移
auto offsets = session2.GetReceivedOffsets();
EXPECT_EQ(offsets[0], 3000u); // 从 3000 继续
EXPECT_EQ(offsets[1], 5000u); // 已完成
// 继续传输
session2.UpdateProgress(0, 3000, 7000);
FileResumeEntry entry;
ASSERT_TRUE(session2.GetFileState(0, entry));
EXPECT_EQ(entry.receivedBytes, 10000u);
}
TEST_F(ResumeScenarioTest, C2CResumeNegotiation) {
// 源客户端查询续传状态
std::vector<std::pair<std::string, uint64_t>> files = {
{"file1.txt", 1000},
{"file2.txt", 2000},
{"file3.txt", 3000}
};
// 目标客户端已有部分数据
std::map<uint32_t, uint64_t> receivedOffsets = {
{0, 500}, // file1: 50%
{1, 2000}, // file2: 100%
{2, 0} // file3: 0%
};
// 构建响应
auto response = BuildResumeResponse(100, 200, receivedOffsets);
// 源客户端解析响应
std::map<uint32_t, uint64_t> parsedOffsets;
ASSERT_TRUE(ParseResumeResponse(response.data(), response.size(), parsedOffsets));
// 根据偏移决定发送策略
for (size_t i = 0; i < files.size(); ++i) {
uint32_t fileIndex = static_cast<uint32_t>(i);
uint64_t startOffset = parsedOffsets[fileIndex];
if (startOffset >= files[i].second) {
// 已完成,跳过
EXPECT_EQ(i, 1u) << "Only file2 should be complete";
} else if (startOffset > 0) {
// 部分完成,从偏移继续
EXPECT_EQ(i, 0u) << "Only file1 should be partial";
EXPECT_EQ(startOffset, 500u);
} else {
// 未开始,从头发送
EXPECT_EQ(i, 2u) << "Only file3 should start from beginning";
}
}
}
TEST_F(ResumeScenarioTest, LargeFileResume) {
ResumeStateManager mgr;
uint64_t fileSize = 10ULL * 1024 * 1024 * 1024; // 10 GB
mgr.Initialize(1, 0, 0, "/data/");
mgr.AddFile(0, fileSize, "huge.bin");
// 模拟接收了 5 GB
mgr.UpdateProgress(0, 0, 5ULL * 1024 * 1024 * 1024);
auto offsets = mgr.GetReceivedOffsets();
EXPECT_EQ(offsets[0], 5ULL * 1024 * 1024 * 1024);
// 序列化/反序列化
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.fileSize, 10ULL * 1024 * 1024 * 1024);
EXPECT_EQ(entry.receivedBytes, 5ULL * 1024 * 1024 * 1024);
}
// ============================================
// 边界条件测试
// ============================================
class ResumeBoundaryTest : public ::testing::Test {};
TEST_F(ResumeBoundaryTest, EmptyFileName) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 100, "");
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.relativePath, "");
}
TEST_F(ResumeBoundaryTest, SpecialCharactersInPath) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "C:\\My Files\\测试目录\\");
mgr.AddFile(0, 100, "文件 (1).txt");
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetTargetDir(), "C:\\My Files\\测试目录\\");
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.relativePath, "文件 (1).txt");
}
TEST_F(ResumeBoundaryTest, ManyRanges) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
mgr.AddFile(0, 100000, "fragmented.bin");
// 添加很多小区间
for (uint64_t i = 0; i < 100000; i += 20) {
mgr.UpdateProgress(0, i, 10);
}
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
FileResumeEntry entry;
ASSERT_TRUE(mgr2.GetFileState(0, entry));
EXPECT_EQ(entry.receivedRanges.size(), 5000u);
}
TEST_F(ResumeBoundaryTest, MaxFileCount) {
ResumeStateManager mgr;
mgr.Initialize(1, 0, 0, "");
// 添加大量文件
for (uint32_t i = 0; i < 1000; ++i) {
mgr.AddFile(i, 100 * (i + 1), "file" + std::to_string(i) + ".txt");
}
auto buffer = mgr.Serialize();
ResumeStateManager mgr2;
ASSERT_TRUE(mgr2.Deserialize(buffer));
EXPECT_EQ(mgr2.GetFileCount(), 1000u);
}

View File

@@ -0,0 +1,647 @@
/**
* @file SHA256VerifyTest.cpp
* @brief SHA-256 文件校验测试
*
* 测试覆盖:
* - SHA-256 哈希计算
* - FileCompletePacketV2 构建与解析
* - 文件完整性校验逻辑
* - 校验失败处理
*/
#include <gtest/gtest.h>
#include <cstring>
#include <vector>
#include <array>
#include <string>
#include <iomanip>
#include <sstream>
// ============================================
// 简化版 SHA-256 实现(测试专用)
// 生产环境使用 OpenSSL 或 Windows CNG
// ============================================
class SHA256 {
public:
SHA256() { Reset(); }
void Reset() {
m_state[0] = 0x6a09e667;
m_state[1] = 0xbb67ae85;
m_state[2] = 0x3c6ef372;
m_state[3] = 0xa54ff53a;
m_state[4] = 0x510e527f;
m_state[5] = 0x9b05688c;
m_state[6] = 0x1f83d9ab;
m_state[7] = 0x5be0cd19;
m_bitLen = 0;
m_bufferLen = 0;
}
void Update(const uint8_t* data, size_t len) {
for (size_t i = 0; i < len; ++i) {
m_buffer[m_bufferLen++] = data[i];
if (m_bufferLen == 64) {
Transform();
m_bitLen += 512;
m_bufferLen = 0;
}
}
}
void Update(const std::vector<uint8_t>& data) {
Update(data.data(), data.size());
}
std::array<uint8_t, 32> Finalize() {
size_t i = m_bufferLen;
// Pad
if (m_bufferLen < 56) {
m_buffer[i++] = 0x80;
while (i < 56) m_buffer[i++] = 0x00;
} else {
m_buffer[i++] = 0x80;
while (i < 64) m_buffer[i++] = 0x00;
Transform();
memset(m_buffer, 0, 56);
}
// Append length
m_bitLen += m_bufferLen * 8;
m_buffer[63] = static_cast<uint8_t>(m_bitLen);
m_buffer[62] = static_cast<uint8_t>(m_bitLen >> 8);
m_buffer[61] = static_cast<uint8_t>(m_bitLen >> 16);
m_buffer[60] = static_cast<uint8_t>(m_bitLen >> 24);
m_buffer[59] = static_cast<uint8_t>(m_bitLen >> 32);
m_buffer[58] = static_cast<uint8_t>(m_bitLen >> 40);
m_buffer[57] = static_cast<uint8_t>(m_bitLen >> 48);
m_buffer[56] = static_cast<uint8_t>(m_bitLen >> 56);
Transform();
// Output
std::array<uint8_t, 32> hash;
for (int j = 0; j < 8; ++j) {
hash[j * 4 + 0] = (m_state[j] >> 24) & 0xff;
hash[j * 4 + 1] = (m_state[j] >> 16) & 0xff;
hash[j * 4 + 2] = (m_state[j] >> 8) & 0xff;
hash[j * 4 + 3] = m_state[j] & 0xff;
}
return hash;
}
static std::string HashToHex(const std::array<uint8_t, 32>& hash) {
std::ostringstream ss;
for (uint8_t b : hash) {
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(b);
}
return ss.str();
}
private:
static uint32_t RotR(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); }
static uint32_t Ch(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); }
static uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); }
static uint32_t Sig0(uint32_t x) { return RotR(x, 2) ^ RotR(x, 13) ^ RotR(x, 22); }
static uint32_t Sig1(uint32_t x) { return RotR(x, 6) ^ RotR(x, 11) ^ RotR(x, 25); }
static uint32_t sig0(uint32_t x) { return RotR(x, 7) ^ RotR(x, 18) ^ (x >> 3); }
static uint32_t sig1(uint32_t x) { return RotR(x, 17) ^ RotR(x, 19) ^ (x >> 10); }
void Transform() {
static const uint32_t K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
uint32_t W[64];
for (int i = 0; i < 16; ++i) {
W[i] = (m_buffer[i * 4] << 24) | (m_buffer[i * 4 + 1] << 16) |
(m_buffer[i * 4 + 2] << 8) | m_buffer[i * 4 + 3];
}
for (int i = 16; i < 64; ++i) {
W[i] = sig1(W[i - 2]) + W[i - 7] + sig0(W[i - 15]) + W[i - 16];
}
uint32_t a = m_state[0], b = m_state[1], c = m_state[2], d = m_state[3];
uint32_t e = m_state[4], f = m_state[5], g = m_state[6], h = m_state[7];
for (int i = 0; i < 64; ++i) {
uint32_t t1 = h + Sig1(e) + Ch(e, f, g) + K[i] + W[i];
uint32_t t2 = Sig0(a) + Maj(a, b, c);
h = g; g = f; f = e; e = d + t1;
d = c; c = b; b = a; a = t1 + t2;
}
m_state[0] += a; m_state[1] += b; m_state[2] += c; m_state[3] += d;
m_state[4] += e; m_state[5] += f; m_state[6] += g; m_state[7] += h;
}
uint32_t m_state[8];
uint8_t m_buffer[64];
uint64_t m_bitLen;
size_t m_bufferLen;
};
// ============================================
// 协议结构(测试专用)
// ============================================
#pragma pack(push, 1)
struct FileCompletePacketV2 {
uint8_t cmd;
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint8_t sha256[32];
};
enum FileErrorV2 : uint8_t {
FEV2_OK = 0,
FEV2_TARGET_OFFLINE = 1,
FEV2_VERSION_MISMATCH = 2,
FEV2_FILE_NOT_FOUND = 3,
FEV2_ACCESS_DENIED = 4,
FEV2_DISK_FULL = 5,
FEV2_TRANSFER_CANCEL = 6,
FEV2_CHECKSUM_ERROR = 7,
FEV2_HASH_MISMATCH = 8,
};
#pragma pack(pop)
// ============================================
// 辅助函数
// ============================================
std::vector<uint8_t> BuildFileCompletePacket(
uint64_t transferID,
uint64_t srcClientID,
uint64_t dstClientID,
uint32_t fileIndex,
uint64_t fileSize,
const std::array<uint8_t, 32>& sha256)
{
std::vector<uint8_t> buffer(sizeof(FileCompletePacketV2));
FileCompletePacketV2* pkt = reinterpret_cast<FileCompletePacketV2*>(buffer.data());
pkt->cmd = 91; // COMMAND_FILE_COMPLETE_V2
pkt->transferID = transferID;
pkt->srcClientID = srcClientID;
pkt->dstClientID = dstClientID;
pkt->fileIndex = fileIndex;
pkt->fileSize = fileSize;
memcpy(pkt->sha256, sha256.data(), 32);
return buffer;
}
bool ParseFileCompletePacket(
const uint8_t* buffer, size_t len,
FileCompletePacketV2& pkt)
{
if (len < sizeof(FileCompletePacketV2)) {
return false;
}
memcpy(&pkt, buffer, sizeof(FileCompletePacketV2));
return true;
}
// 校验文件完整性
FileErrorV2 VerifyFileIntegrity(
const std::array<uint8_t, 32>& expectedHash,
const std::vector<uint8_t>& fileData)
{
SHA256 sha;
sha.Update(fileData);
auto actualHash = sha.Finalize();
if (actualHash == expectedHash) {
return FEV2_OK;
}
return FEV2_HASH_MISMATCH;
}
// ============================================
// SHA-256 基础测试
// ============================================
class SHA256Test : public ::testing::Test {};
TEST_F(SHA256Test, EmptyString) {
SHA256 sha;
auto hash = sha.Finalize();
// SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
std::string hex = SHA256::HashToHex(hash);
EXPECT_EQ(hex, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
}
TEST_F(SHA256Test, HelloWorld) {
SHA256 sha;
const char* msg = "Hello, World!";
sha.Update(reinterpret_cast<const uint8_t*>(msg), strlen(msg));
auto hash = sha.Finalize();
// SHA-256("Hello, World!") = dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f
std::string hex = SHA256::HashToHex(hash);
EXPECT_EQ(hex, "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f");
}
TEST_F(SHA256Test, ABC) {
SHA256 sha;
const char* msg = "abc";
sha.Update(reinterpret_cast<const uint8_t*>(msg), strlen(msg));
auto hash = sha.Finalize();
// SHA-256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
std::string hex = SHA256::HashToHex(hash);
EXPECT_EQ(hex, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
}
TEST_F(SHA256Test, IncrementalUpdate) {
SHA256 sha1, sha2;
const char* part1 = "Hello, ";
const char* part2 = "World!";
const char* full = "Hello, World!";
sha1.Update(reinterpret_cast<const uint8_t*>(part1), strlen(part1));
sha1.Update(reinterpret_cast<const uint8_t*>(part2), strlen(part2));
sha2.Update(reinterpret_cast<const uint8_t*>(full), strlen(full));
auto hash1 = sha1.Finalize();
auto hash2 = sha2.Finalize();
EXPECT_EQ(hash1, hash2);
}
TEST_F(SHA256Test, LargeData) {
SHA256 sha;
// 1 MB of zeros
std::vector<uint8_t> data(1024 * 1024, 0);
sha.Update(data);
auto hash = sha.Finalize();
// 验证计算成功(不崩溃)
EXPECT_EQ(hash.size(), 32u);
}
TEST_F(SHA256Test, BinaryData) {
SHA256 sha;
std::vector<uint8_t> data = {0x00, 0x01, 0x02, 0x03, 0xff, 0xfe, 0xfd, 0xfc};
sha.Update(data);
auto hash = sha.Finalize();
// 验证计算成功
EXPECT_EQ(hash.size(), 32u);
}
TEST_F(SHA256Test, Reset) {
SHA256 sha;
sha.Update(reinterpret_cast<const uint8_t*>("test"), 4);
sha.Reset();
auto hash = sha.Finalize();
// 重置后应该等于空字符串的哈希
std::string hex = SHA256::HashToHex(hash);
EXPECT_EQ(hex, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
}
// ============================================
// FileCompletePacketV2 测试
// ============================================
class FileCompletePacketTest : public ::testing::Test {};
TEST_F(FileCompletePacketTest, BuildAndParse) {
std::array<uint8_t, 32> sha256 = {};
sha256[0] = 0xAA;
sha256[31] = 0xBB;
auto buffer = BuildFileCompletePacket(12345, 100, 200, 5, 1024 * 1024, sha256);
FileCompletePacketV2 pkt;
ASSERT_TRUE(ParseFileCompletePacket(buffer.data(), buffer.size(), pkt));
EXPECT_EQ(pkt.cmd, 91);
EXPECT_EQ(pkt.transferID, 12345u);
EXPECT_EQ(pkt.srcClientID, 100u);
EXPECT_EQ(pkt.dstClientID, 200u);
EXPECT_EQ(pkt.fileIndex, 5u);
EXPECT_EQ(pkt.fileSize, 1024u * 1024u);
EXPECT_EQ(pkt.sha256[0], 0xAA);
EXPECT_EQ(pkt.sha256[31], 0xBB);
}
TEST_F(FileCompletePacketTest, TruncatedPacket) {
std::array<uint8_t, 32> sha256 = {};
auto buffer = BuildFileCompletePacket(1, 0, 0, 0, 100, sha256);
// 截断
FileCompletePacketV2 pkt;
EXPECT_FALSE(ParseFileCompletePacket(buffer.data(), sizeof(FileCompletePacketV2) - 10, pkt));
}
TEST_F(FileCompletePacketTest, LargeFileSize) {
std::array<uint8_t, 32> sha256 = {};
uint64_t largeSize = 100ULL * 1024 * 1024 * 1024; // 100 GB
auto buffer = BuildFileCompletePacket(1, 0, 0, 0, largeSize, sha256);
FileCompletePacketV2 pkt;
ASSERT_TRUE(ParseFileCompletePacket(buffer.data(), buffer.size(), pkt));
EXPECT_EQ(pkt.fileSize, largeSize);
}
// ============================================
// 文件完整性校验测试
// ============================================
class FileIntegrityTest : public ::testing::Test {};
TEST_F(FileIntegrityTest, CorrectHash) {
std::vector<uint8_t> fileData = {'H', 'e', 'l', 'l', 'o'};
SHA256 sha;
sha.Update(fileData);
auto expectedHash = sha.Finalize();
auto result = VerifyFileIntegrity(expectedHash, fileData);
EXPECT_EQ(result, FEV2_OK);
}
TEST_F(FileIntegrityTest, IncorrectHash) {
std::vector<uint8_t> fileData = {'H', 'e', 'l', 'l', 'o'};
std::array<uint8_t, 32> wrongHash = {}; // 全零的错误哈希
auto result = VerifyFileIntegrity(wrongHash, fileData);
EXPECT_EQ(result, FEV2_HASH_MISMATCH);
}
TEST_F(FileIntegrityTest, CorruptedData) {
std::vector<uint8_t> originalData = {'H', 'e', 'l', 'l', 'o'};
SHA256 sha;
sha.Update(originalData);
auto originalHash = sha.Finalize();
// 修改一个字节
std::vector<uint8_t> corruptedData = originalData;
corruptedData[2] = 'X';
auto result = VerifyFileIntegrity(originalHash, corruptedData);
EXPECT_EQ(result, FEV2_HASH_MISMATCH);
}
TEST_F(FileIntegrityTest, EmptyFile) {
std::vector<uint8_t> emptyData;
SHA256 sha;
auto emptyHash = sha.Finalize();
auto result = VerifyFileIntegrity(emptyHash, emptyData);
EXPECT_EQ(result, FEV2_OK);
}
TEST_F(FileIntegrityTest, SingleBitDifference) {
std::vector<uint8_t> data1 = {0x00};
std::vector<uint8_t> data2 = {0x01}; // 单比特差异
SHA256 sha1, sha2;
sha1.Update(data1);
sha2.Update(data2);
auto hash1 = sha1.Finalize();
auto hash2 = sha2.Finalize();
// 哈希应该完全不同
EXPECT_NE(hash1, hash2);
// 验证错误检测
auto result = VerifyFileIntegrity(hash1, data2);
EXPECT_EQ(result, FEV2_HASH_MISMATCH);
}
// ============================================
// 流式哈希计算测试
// ============================================
class StreamingHashTest : public ::testing::Test {};
TEST_F(StreamingHashTest, ChunkedUpdate) {
std::vector<uint8_t> fullData(10000);
for (size_t i = 0; i < fullData.size(); ++i) {
fullData[i] = static_cast<uint8_t>(i & 0xFF);
}
// 一次性计算
SHA256 sha1;
sha1.Update(fullData);
auto hash1 = sha1.Finalize();
// 分块计算
SHA256 sha2;
size_t chunkSize = 64; // SHA-256 块大小
for (size_t i = 0; i < fullData.size(); i += chunkSize) {
size_t len = std::min(chunkSize, fullData.size() - i);
sha2.Update(fullData.data() + i, len);
}
auto hash2 = sha2.Finalize();
EXPECT_EQ(hash1, hash2);
}
TEST_F(StreamingHashTest, VariableChunkSizes) {
std::vector<uint8_t> data(1000);
for (size_t i = 0; i < data.size(); ++i) {
data[i] = static_cast<uint8_t>(i);
}
SHA256 sha1;
sha1.Update(data);
auto expected = sha1.Finalize();
// 不同大小的块
std::vector<size_t> chunkSizes = {1, 7, 63, 64, 65, 128, 1000};
for (size_t chunkSize : chunkSizes) {
SHA256 sha2;
for (size_t i = 0; i < data.size(); i += chunkSize) {
size_t len = std::min(chunkSize, data.size() - i);
sha2.Update(data.data() + i, len);
}
auto actual = sha2.Finalize();
EXPECT_EQ(expected, actual) << "Failed for chunk size: " << chunkSize;
}
}
// ============================================
// 文件传输完整性验证场景
// ============================================
class TransferVerificationTest : public ::testing::Test {};
TEST_F(TransferVerificationTest, SimulateSuccessfulTransfer) {
// 模拟文件数据
std::vector<uint8_t> fileData(1024 * 64); // 64 KB
for (size_t i = 0; i < fileData.size(); ++i) {
fileData[i] = static_cast<uint8_t>(i * 17); // 伪随机数据
}
// 发送方计算哈希
SHA256 senderSha;
senderSha.Update(fileData);
auto senderHash = senderSha.Finalize();
// 构建校验包
auto packet = BuildFileCompletePacket(12345, 100, 0, 0, fileData.size(), senderHash);
// 接收方解析并验证
FileCompletePacketV2 pkt;
ASSERT_TRUE(ParseFileCompletePacket(packet.data(), packet.size(), pkt));
EXPECT_EQ(pkt.fileSize, fileData.size());
// 接收方计算哈希并比较
std::array<uint8_t, 32> expectedHash;
memcpy(expectedHash.data(), pkt.sha256, 32);
auto result = VerifyFileIntegrity(expectedHash, fileData);
EXPECT_EQ(result, FEV2_OK);
}
TEST_F(TransferVerificationTest, SimulateCorruptedTransfer) {
// 原始文件
std::vector<uint8_t> originalData(1024);
for (size_t i = 0; i < originalData.size(); ++i) {
originalData[i] = static_cast<uint8_t>(i);
}
// 发送方计算哈希
SHA256 senderSha;
senderSha.Update(originalData);
auto senderHash = senderSha.Finalize();
// 模拟传输中数据损坏
std::vector<uint8_t> receivedData = originalData;
receivedData[512] ^= 0xFF; // 翻转一个字节
// 校验失败
auto result = VerifyFileIntegrity(senderHash, receivedData);
EXPECT_EQ(result, FEV2_HASH_MISMATCH);
}
TEST_F(TransferVerificationTest, MultipleFilesInTransfer) {
struct FileInfo {
std::vector<uint8_t> data;
std::array<uint8_t, 32> hash;
};
std::vector<FileInfo> files(5);
// 生成测试文件
for (int i = 0; i < 5; ++i) {
files[i].data.resize(1000 * (i + 1));
for (size_t j = 0; j < files[i].data.size(); ++j) {
files[i].data[j] = static_cast<uint8_t>(j + i * 7);
}
SHA256 sha;
sha.Update(files[i].data);
files[i].hash = sha.Finalize();
}
// 验证每个文件
for (int i = 0; i < 5; ++i) {
auto result = VerifyFileIntegrity(files[i].hash, files[i].data);
EXPECT_EQ(result, FEV2_OK) << "File " << i << " verification failed";
}
// 交叉验证应该失败
auto crossResult = VerifyFileIntegrity(files[0].hash, files[1].data);
EXPECT_EQ(crossResult, FEV2_HASH_MISMATCH);
}
// ============================================
// 边界条件测试
// ============================================
class HashBoundaryTest : public ::testing::Test {};
TEST_F(HashBoundaryTest, ExactBlockSize) {
// SHA-256 块大小是 64 字节
std::vector<uint8_t> data(64, 'A');
SHA256 sha;
sha.Update(data);
auto hash = sha.Finalize();
EXPECT_EQ(hash.size(), 32u);
}
TEST_F(HashBoundaryTest, BlockSizePlusOne) {
std::vector<uint8_t> data(65, 'B');
SHA256 sha;
sha.Update(data);
auto hash = sha.Finalize();
EXPECT_EQ(hash.size(), 32u);
}
TEST_F(HashBoundaryTest, BlockSizeMinusOne) {
std::vector<uint8_t> data(63, 'C');
SHA256 sha;
sha.Update(data);
auto hash = sha.Finalize();
EXPECT_EQ(hash.size(), 32u);
}
TEST_F(HashBoundaryTest, MultipleBlocks) {
std::vector<uint8_t> data(64 * 10, 'D');
SHA256 sha;
sha.Update(data);
auto hash = sha.Finalize();
EXPECT_EQ(hash.size(), 32u);
}
TEST_F(HashBoundaryTest, PaddingBoundary55) {
// 55 字节需要特殊处理padding + length 刚好 64
std::vector<uint8_t> data(55, 'E');
SHA256 sha;
sha.Update(data);
auto hash = sha.Finalize();
EXPECT_EQ(hash.size(), 32u);
}
TEST_F(HashBoundaryTest, PaddingBoundary56) {
// 56 字节需要额外的块
std::vector<uint8_t> data(56, 'F');
SHA256 sha;
sha.Update(data);
auto hash = sha.Finalize();
EXPECT_EQ(hash.size(), 32u);
}