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

555
docs/FileTransferV2_Plan.md Normal file
View File

@@ -0,0 +1,555 @@
# 文件传输 V2 协议方案
> 支持 C2C客户端到客户端传输 + 断点续传
## 实施进度
| 阶段 | 内容 | 状态 | 完成日期 |
|------|------|------|---------|
| Phase 1 | V2 结构体 + 接口定义 | ✅ 完成 | 2026-02-23 |
| Phase 2 | V2 基础收发(不含 C2C | ✅ 完成 | 2026-02-23 |
| Phase 3 | 断点续传 | ✅ 完成 | 2026-02-23 |
| Phase 4 | C2C | ✅ 完成 | 2026-02-23 |
| Phase 5 | SHA-256 文件校验 | ✅ 完成 | 2026-02-26 |
| Phase 6 | 能力协商 + 服务端开关 + 缓存整合 | ✅ 完成 | 2026-02-27 |
---
## 一、命令设计
```cpp
// common/commands.h
// 老命令(保持不变)
COMMAND_SEND_FILE = 68, // V1 文件传输
// 新命令V2 独立)
COMMAND_SEND_FILE_V2 = 85, // V2 文件传输(支持 C2C + 断点续传)
COMMAND_FILE_RESUME = 86, // V2 断点续传控制
COMMAND_CLIPBOARD_V2 = 87, // V2 剪贴板请求C2C
COMMAND_FILE_QUERY_RESUME = 88, // V2 断点续传查询
COMMAND_C2C_PREPARE = 89, // C2C 准备接收通知
COMMAND_C2C_TEXT = 90, // C2C 文本传输
COMMAND_FILE_COMPLETE_V2 = 91, // V2 文件完成校验SHA-256
COMMAND_C2C_PREPARE_RESP = 92, // C2C 准备响应(返回目标目录)
// 客户端能力位
#define CLIENT_CAP_V2 0x0001 // 支持 V2 文件传输
// 功能引入日期
#define FILE_TRANSFER_V2_DATE "Feb 27 2026"
```
---
## 二、结构体设计
```cpp
// common/file_upload.h
#pragma pack(push, 1)
// ==================== V1 协议(不改动)====================
struct FileChunkPacket {
uint8_t cmd; // = 68
uint32_t fileIndex;
uint32_t totalNum;
uint64_t fileSize;
uint64_t offset;
uint64_t dataLength;
uint64_t nameLength;
}; // 41 bytes
// ==================== V2 协议(新增,独立)====================
struct FileChunkPacketV2 {
uint8_t cmd; // = 85
uint64_t transferID; // 传输会话ID
uint64_t srcClientID; // 源客户端 (0=主控端)
uint64_t dstClientID; // 目标客户端 (0=主控端)
uint32_t fileIndex; // 文件编号
uint32_t totalFiles; // 总文件数
uint64_t fileSize; // 文件大小
uint64_t offset; // 偏移
uint64_t dataLength; // 数据长度
uint64_t nameLength; // 文件名长度
uint16_t flags; // 标志位
uint16_t checksum; // CRC16
uint8_t reserved[8]; // 预留
}; // 81 bytes
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 FileResumePacketV2 {
uint8_t cmd; // = 86
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint64_t receivedBytes;
uint16_t flags;
uint16_t rangeCount;
// FileRangeV2 ranges[rangeCount];
}; // 51 bytes + ranges
struct FileRangeV2 {
uint64_t offset;
uint64_t length;
}; // 16 bytes
struct ClipboardRequestV2 {
uint8_t cmd; // = 87
uint64_t srcClientID;
uint64_t dstClientID;
uint64_t transferID;
char hash[64];
char hmac[16];
}; // 105 bytes
struct C2CPreparePacket {
uint8_t cmd; // = 89
uint64_t transferID;
uint64_t srcClientID; // 发送方客户端ID
}; // 17 bytes
struct C2CPrepareRespPacket {
uint8_t cmd; // = 92
uint64_t transferID;
uint64_t srcClientID; // 原始发送方客户端ID
uint16_t pathLength;
// char path[pathLength]; // UTF-8 目标目录
}; // 19 bytes + path
struct FileCompletePacketV2 {
uint8_t cmd; // = 91
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint8_t sha256[32]; // SHA-256 哈希
}; // 69 bytes
#pragma pack(pop)
```
---
## 三、接口设计
```cpp
// common/file_upload.h
// ==================== V1 接口(保持不变)====================
int FileBatchTransferWorker(...);
int RecvFileChunk(...);
// ==================== V2 接口(新增)====================
struct TransferOptionsV2 {
uint64_t transferID; // 0=自动生成
uint64_t srcClientID;
uint64_t dstClientID;
bool enableResume; // 启用断点续传
};
int FileBatchTransferWorkerV2(
const std::vector<std::string>& files,
const std::string& targetDir,
void* user,
OnTransformV2 f,
OnFinish finish,
const std::string& hash,
const std::string& hmac,
const TransferOptionsV2& options
);
int RecvFileChunkV2(
char* buf, size_t len,
void* user,
OnFinish f,
const std::string& hash,
const std::string& hmac,
uint64_t myClientID
);
// 断点续传
uint64_t GenerateTransferID();
bool SaveResumeState(uint64_t transferID, ...);
bool LoadResumeState(uint64_t transferID, ...);
void CleanupResumeState(uint64_t transferID);
std::vector<uint64_t> GetPendingTransfers();
// 文件完整性校验
bool HandleFileCompleteV2(
const char* buf, size_t len,
uint64_t myClientID
);
```
---
## 四、持久化设计
### 4.1 状态文件位置
所有状态文件统一存放在:
- Windows: `%TEMP%\FileTransfer\`
- Linux: `/tmp/FileTransfer/`
### 4.2 状态文件类型
| 后缀 | 用途 | 文件名格式 |
|------|------|-----------|
| `.resume` | 断点续传状态 | `{transferID}.resume` |
| `.recv` | C2C 接收方目标目录 | `{transferID}.recv` |
| `.send` | C2C 发送方文件路径 | `{transferID}.send` |
### 4.3 自动清理
- 状态文件超过 7 天自动删除 (`RESUME_EXPIRE_DAYS = 7`)
- 清理在 `GetPendingTransfers()` 时触发
- 三种后缀文件均会被清理
### 4.4 .resume 文件结构
```cpp
struct ResumeFileHeader {
uint32_t magic; // = 0x52455355 "RESU"
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t totalFiles;
char targetDir[260];
};
struct ResumeFileEntry {
uint64_t fileSize;
uint64_t receivedBytes;
uint16_t rangeCount;
char fileName[260];
// FileRangeV2 ranges[rangeCount];
};
```
### 4.5 .recv / .send 文件结构
简单文本文件,存储 UTF-8 编码的目录路径或文件路径列表(每行一个)。
---
## 五、服务端路由
```cpp
// 2015RemoteDlg.cpp
case COMMAND_SEND_FILE: {
// V1 逻辑(保持不变)
}
case COMMAND_SEND_FILE_V2: {
// V2 逻辑(独立)
FileChunkPacketV2* pkt = (FileChunkPacketV2*)szBuffer;
if (pkt->dstClientID == 0) {
HandleLocalReceiveV2(...);
} else {
// C2C 转发
ForwardToClientV2(...);
}
}
case COMMAND_FILE_RESUME: {
// V2 断点续传
}
```
---
## 六、版本检测
### 6.1 能力位协商
```cpp
// common/commands.h
#define CLIENT_CAP_V2 0x0001 // 支持 V2 文件传输
// LOGIN_INFOR 构造函数
sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, CLIENT_CAP_V2);
// 结果示例: "Feb 27 2026-0001"
```
### 6.2 服务端检测
```cpp
// 双重检测:能力位 + 版本日期
bool SupportsFileTransferV2(context* ctx) {
// 1. 检查服务端开关
if (!g_2015RemoteDlg || !g_2015RemoteDlg->m_bEnableFileV2) return false;
// 2. 检查能力位
if (ctx->SupportsFileV2()) return true;
// 3. 兼容旧版:检查版本日期
CString version = ctx->GetClientData(ONLINELIST_VERSION);
return IsDateGreaterOrEqual(version, FILE_TRANSFER_V2_DATE);
}
// C2C 要求双方都支持 V2
if (!SupportsFileTransferV2(src) || !SupportsFileTransferV2(dst)) {
// 提示版本不支持
}
```
### 6.3 服务端开关
| 菜单位置 | 默认值 | 配置键 |
|---------|--------|--------|
| 参数 → 文件传输V2 | 关闭 | `settings/EnableFileV2` |
- 未勾选时使用 V1 协议
- 勾选后根据客户端能力选择 V1/V2
- 配置通过 `THIS_CFG` 持久化
---
## 七、行为矩阵
| 场景 | 源版本 | 目标版本 | 协议 | 结果 |
|------|--------|---------|------|------|
| 本地→远程 | - | V1 | V1 | ✅ 正常 |
| 本地→远程 | - | V2 | V2 | ✅ 正常 + 断点续传 |
| 远程→本地 | V1 | - | V1 | ✅ 正常 |
| 远程→本地 | V2 | - | V2 | ✅ 正常 + 断点续传 |
| C2C | V1 | V1 | - | ❌ 不支持 |
| C2C | V1 | V2 | - | ❌ 不支持(提示升级) |
| C2C | V2 | V1 | - | ❌ 不支持(提示升级) |
| C2C | V2 | V2 | V2 | ✅ 正常 + 断点续传 |
---
## 八、改动文件清单
| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `common/commands.h` | 修改 | 新增命令字、能力位 |
| `common/file_upload.h` | 修改 | 新增 V2 结构体和接口 |
| `SimplePlugins/file_upload.cpp` | 修改 | V2 实现、缓存整合、自动清理 |
| `client/ScreenManager.cpp` | 修改 | 新增 case 分支 |
| `client/KernelManager.cpp` | 修改 | 处理 V2 命令 |
| `server/2015Remote/context.h` | 修改 | 新增能力位列、SupportsFileV2() |
| `server/2015Remote/2015RemoteDlg.h` | 修改 | 新增 m_bEnableFileV2、函数声明 |
| `server/2015Remote/2015RemoteDlg.cpp` | 修改 | case 分支、菜单处理、能力解析 |
| `server/2015Remote/resource.h` | 修改 | 新增菜单ID |
| `server/2015Remote/2015Remote.rc` | 修改 | 新增菜单项 |
| `server/2015Remote/CDlgFileSend.cpp` | 修改 | C2C 校验包转发 |
| `server/2015Remote/ScreenSpyDlg.cpp` | 修改 | 剪贴板同步 |
---
## 九、实施记录
### Phase 1: V2 结构体 + 接口定义 ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `common/commands.h`
- 新增 `FEATURE_FILE_V2 = "Mar 1 2026"`
- 新增 `COMMAND_SEND_FILE_V2 = 85`
- 新增 `COMMAND_FILE_RESUME = 86`
- 新增 `COMMAND_CLIPBOARD_V2 = 87`
- [x] `common/file_upload.h`
- 新增 `FileFlagsV2` 枚举
- 新增 `FileChunkPacketV2` 结构体 (81 bytes)
- 新增 `FileRangeV2` 结构体 (16 bytes)
- 新增 `FileResumePacketV2` 结构体 (51 bytes + ranges)
- 新增 `ClipboardRequestV2` 结构体 (105 bytes)
- 新增 `FileErrorV2` 错误码枚举
- 新增 `TransferOptionsV2` 结构体
- 新增 `FileBatchTransferWorkerV2()` 接口声明
- 新增 `RecvFileChunkV2()` 接口声明
- 新增断点续传相关接口声明
---
### Phase 2: V2 基础收发 ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `SimplePlugins/file_upload.cpp` - 实现 `FileBatchTransferWorkerV2()`
- [x] `SimplePlugins/file_upload.cpp` - 实现 `RecvFileChunkV2()`
- [x] `SimplePlugins/file_upload.cpp` - 实现 `GenerateTransferID()`
- [x] `client/ScreenManager.cpp` - 新增 `COMMAND_SEND_FILE_V2` case
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 新增 `COMMAND_SEND_FILE_V2` case
---
### Phase 3: 断点续传 ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `SimplePlugins/file_upload.cpp` - `.resume` 文件头结构体定义
- [x] `SimplePlugins/file_upload.cpp` - 实现 `GetResumeDir()` / `GetResumeFilePath()`
- [x] `SimplePlugins/file_upload.cpp` - 实现 `SaveResumeState()` 保存传输状态
- [x] `SimplePlugins/file_upload.cpp` - 实现 `LoadResumeState()` 恢复传输状态
- [x] `SimplePlugins/file_upload.cpp` - 实现 `CleanupResumeState()` 清理状态文件
- [x] `SimplePlugins/file_upload.cpp` - 实现 `GetPendingTransfers()` 枚举未完成传输
- [x] `SimplePlugins/file_upload.cpp` - `RecvFileChunkV2()` 中定期自动保存状态
- [x] `client/ScreenManager.cpp` - `OnReconnect()` 中检测并恢复未完成传输
- [x] `client/ScreenManager.cpp` - 处理 `COMMAND_FILE_RESUME` 续传控制
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 处理/转发 `COMMAND_FILE_RESUME`
**实现细节**:
- `.resume` 文件存储位置: `%TEMP%\FileTransfer\{transferID}.resume`
- 自动保存触发条件: 每 5 秒或每 1MB 新数据
- 重连时自动恢复本地状态,等待数据继续传输
- 所有文件完成后自动清理 `.resume` 文件
---
### Phase 4: C2C ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 键盘钩子新增静态变量 `remoteCtrlCTime`
- [x] `server/2015Remote/2015RemoteDlg.cpp` - Ctrl+C 时区分本地/远程,记录时间
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 键盘钩子分支[3]远程A→远程B
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 发送 `COMMAND_CLIPBOARD_V2` 触发 C2C
- [x] `client/ScreenManager.cpp` - 处理 `COMMAND_CLIPBOARD_V2`,获取剪贴板并发送到目标
- [x] `SimplePlugins/file_upload.cpp` - 新增 C2C 文件跟踪 (`g_c2cReceivedFiles`)
- [x] `SimplePlugins/file_upload.cpp` - 新增 `SetFilesToClipboard()` 函数
- [x] `SimplePlugins/file_upload.cpp` - `RecvFileChunkV2()` 完成后设置剪贴板
**实现细节**:
- 远程A 按 Ctrl+C → 记录 `operateWnd``remoteCtrlCTime`
- 切换到远程B 按 Ctrl+V → 检测 `operateWnd != dlg` 且时间有效
- 服务端发送 `COMMAND_CLIPBOARD_V2` 到源客户端A
- 客户端A 获取剪贴板文件,使用 V2 协议发送到客户端B
- 客户端B 接收文件,传输完成后自动设置到剪贴板 (CF_HDROP)
---
### Phase 5: SHA-256 文件校验 ✅
**状态**: 已完成
**完成时间**: 2026-02-26
**改动文件**:
- [x] `common/commands.h` - 新增 `COMMAND_FILE_COMPLETE_V2 = 91`
- [x] `common/file_upload.h` - 新增 `FileCompletePacketV2` 结构体 (69 bytes)
- [x] `SimplePlugins/file_upload.cpp` - 实现 `SHA256Context` 类 (Windows bcrypt API)
- [x] `SimplePlugins/file_upload.cpp` - `FileRecvStateV2` 新增 `sha256Ctx` 流式计算
- [x] `SimplePlugins/file_upload.cpp` - 实现 `HandleFileCompleteV2()` 校验函数
- [x] `SimplePlugins/file_upload.cpp` - `FileBatchTransferWorkerV2()` 发送完成后发送校验包
- [x] `client/KernelManager.cpp` - 处理 `COMMAND_FILE_COMPLETE_V2`
- [x] `client/ScreenManager.cpp` - 处理 `COMMAND_FILE_COMPLETE_V2`
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 处理/转发 `COMMAND_FILE_COMPLETE_V2`
- [x] `server/2015Remote/CDlgFileSend.cpp` - 处理/转发 C2C 校验包
**实现细节**:
- 使用 Windows bcrypt API (`BCRYPT_SHA256_ALGORITHM`) 计算 SHA-256
- 接收端在写入数据时同步更新 SHA-256流式计算无需重读文件
- 每个文件传输完成后,发送端发送 `FILE_COMPLETE_V2` 校验包
- 接收端收到校验包后对比本地计算的哈希值
- C2C 场景下服务端负责转发校验包到目标客户端
**支持的传输方向**:
| 方向 | 状态 |
|------|------|
| 客户端 → 服务端 | ✅ 已测试 |
| 服务端 → 客户端 | ✅ 已测试 |
| 客户端A → 客户端B (C2C) | ✅ 已测试 |
---
### Phase 5.1: SHA-256 校验 Bug 修复 ✅
**状态**: 已完成
**完成时间**: 2026-02-26
**修复的问题**:
| 问题 | 原因 | 修复 |
|------|------|------|
| C2C 校验包未转发 | `CDlgFileSend` 未检查 `dstClientID` | 检查并转发到目标客户端 |
| 断点续传校验失败 | 从 `.resume` 恢复时 `sha256Valid` 仍为 true | 恢复时设为 false强制从文件重算 |
| 已完成状态被复用 | 内存匹配未排除已完成的状态 | 增加 `receivedBytes < fileSize` 条件 |
| 不同目录文件被误匹配 | C2C 续传按文件名匹配,忽略目标目录 | 匹配时包含目标目录 |
**改动文件**:
- [x] `server/2015Remote/CDlgFileSend.cpp` - C2C 校验包转发
- [x] `SimplePlugins/file_upload.cpp` - 断点续传时设 `sha256Valid=false`
- [x] `SimplePlugins/file_upload.cpp` - 内存状态匹配跳过已完成
- [x] `SimplePlugins/file_upload.cpp` - C2C 续传匹配包含目标目录
**设计决策**:
- C2C 断点续传匹配时包含目标目录,避免跨目录误匹配
- 断点续传时从文件重新计算 SHA-256无法恢复流式上下文
- 校验失败自动删除损坏文件
---
### Phase 6: 能力协商 + 服务端开关 + 缓存整合 ✅
**状态**: 已完成
**完成时间**: 2026-02-27
**改动内容**:
1. **能力位协商**
- `LOGIN_INFOR` 构造函数在版本字符串后附加能力位(格式:`Feb 27 2026-0001`
- `context.h` 新增 `ONLINELIST_CAPABILITIES` 列、`SupportsFileV2()` 方法
- `AddList()` 解析能力位并存储到列表项
2. **服务端开关**
- 参数菜单新增 "文件传输V2" 选项(`ID_PARAM_FILE_V2`
- `m_bEnableFileV2` 类成员控制 V2 开关
- 配置通过 `THIS_CFG` 持久化到 `settings/EnableFileV2`
- `SupportsFileTransferV2()` 统一判断函数
3. **缓存目录整合**
- 原目录:
- `%TEMP%\FileTransfer\` (.resume)
- `%LOCALAPPDATA%\ServerD11\c2c_recv_targets\` (.target)
- `%LOCALAPPDATA%\ServerD11\c2c_targets\` (.target)
- 整合后:`%TEMP%\FileTransfer\` 统一存放 `.resume``.recv``.send`
- 移除 `GetC2CTargetDir()` 函数
- `CleanupExpiredStateFiles()` 处理三种文件类型
4. **自动清理**
- `RESUME_EXPIRE_DAYS = 7`
- `GetPendingTransfers()` 时触发清理
- 检查 `ftLastWriteTime` 判断过期
**改动文件**:
- [x] `common/commands.h` - 新增 `CLIENT_CAP_V2`
- [x] `server/2015Remote/context.h` - 新增 `ONLINELIST_CAPABILITIES``SupportsFileV2()`
- [x] `server/2015Remote/2015RemoteDlg.h` - 新增 `m_bEnableFileV2`、声明 `SupportsFileTransferV2()`
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 实现菜单处理、能力解析、判断函数
- [x] `server/2015Remote/resource.h` - 新增 `ID_PARAM_FILE_V2`
- [x] `server/2015Remote/2015Remote.rc` - 新增菜单项
- [x] `SimplePlugins/file_upload.cpp` - 整合缓存目录、自动清理