fix(app): OTA 信任链加固、引擎背压与协议正确性

OTA(update/):
- 解压前校验 7z 条目清单:sevenz-rust 0.6.1 的 decompress_file 直接 dest.join(entry.name()),不校验 ".."、绝对路径与盘符,恶意归档可越界写文件;现在要求恰好一个条目且名字与约定二进制一致(fail-closed)
- 无 .sha256 侧车时拒绝安装,不再静默跳过校验而 UI 仍宣称已校验
- 安装前重算暂存文件哈希;同意标记从仅版本号改为绑定「版本 + 哈希」
- 跨卷 rename 失败回退「复制 + fsync + 删源」(暂存在 APPDATA、exe 在安装目录,不同卷时必失败且报错误导)
- .old 回滚备份保留 7 天:原实现新版本首启即删,新版本一旦崩溃用户无任何回退路径
- 新增安装互斥锁:两实例同时安装会互删暂存包、争抢同一个 .old
- platform_triple 用 std::env::consts::ARCH:写死 x86_64 让 ARM 客户端永远挑不到产物
- pick_assets 按命名约定精确后缀匹配:非 Windows 下 ends_with("") 恒真,.tar.gz/.deb 会被当成可执行文件下载并替换自身

引擎(mqtt/):
- 事件通道改有界并分级:高频事件(消息/日志)满即丢并计数、每 1000 条提示一次;状态/订阅结果/发布回执异步补投。原无界通道在到达速率超过消费速率时内存无上界(单条最长 128KB)
- 重连改指数退避(1s→30s 封顶)+ 0~500ms 抖动,替代固定 2 秒的惊群式重试
- 测试连接的建连(读证书,阻塞 IO)移入 spawn_blocking,与 connect 一致

正确性与性能:
- session_params_changed 补齐 4 个 v5 CONNECT 属性(会话过期/接收上限/最大报文/主题别名),漏掉会让"重连后生效"提示静默失效;测试改逐字段防漏式断言
- ConnectionConfig / AliyunPreset 手写脱敏 Debug:password 与 AccessKeySecret/DeviceSecret 输出 <redacted>,杜绝一次 {:?} 把凭据送进 stderr 与 Sentry
- MqttRecord.user_properties 改 Arc<[(String,String)]>、应用日志改 Arc<LogEntry>:消息面板每帧 300 条、日志面板每帧 3000 条的深拷贝降为引用计数
- 阿里云 DeviceName 空值校验,避免拼出非法 clientId 后服务端报错难懂
This commit is contained in:
2026-09-30 10:15:40 +08:00
parent 2661a734ee
commit 9b6df8b9eb
15 changed files with 451 additions and 90 deletions
+10 -4
View File
@@ -44,6 +44,8 @@ mod titlebar;
use theme::apply_theme;
const MAX_LOGS: usize = 3000;
/// 引擎 → UI 的事件队列容量(有界,见 `MqttEngine::emit` 的丢弃策略)
const EVENT_QUEUE_CAPACITY: usize = 1024;
// 应用级快捷键动作(context=None:任意焦点状态下都匹配)。
gpui_kit::actions!(mqttx, [NewConnection, OpenSettings, ExportConnections, ImportConnections, OpenResourceMonitor]);
@@ -92,7 +94,9 @@ pub struct MqttXApp {
pub errors: HashMap<String, String>,
/// 每连接消息环形缓冲:键为驻留后的连接 ID,条数 + 字节预算双重约束
pub messages: HashMap<Arc<str>, MessageRing>,
pub logs: VecDeque<LogEntry>,
/// Arc 共享:日志面板每帧最多克隆 3000 条,深拷贝会让渲染线程每秒
/// 产生数十万次 String 堆分配;改 Arc 后仅增加引用计数
pub logs: VecDeque<Arc<LogEntry>>,
pub seq: u64,
// ── 标签与视图 ──
@@ -126,7 +130,9 @@ impl MqttXApp {
apply_theme(settings.theme, cx);
let (tx, rx) = smol::channel::unbounded();
// 有界事件队列:引擎侧对高频事件(消息/日志)做丢弃并计数,保证 UI 消费
// 不过来时内存有上界。1024 × 单条最大 128KB ≈ 最坏 128MB,常规消息下几百 KB。
let (tx, rx) = smol::channel::bounded(EVENT_QUEUE_CAPACITY);
let engine = MqttEngine::new(tx);
let weak = cx.entity().downgrade();
@@ -163,7 +169,7 @@ impl MqttXApp {
window.open_dialog(cx, move |dialog, _, _cx| {
let staged = staged.clone();
let staged_skip = staged.clone();
let ver = staged.version.clone();
let staged_consent = staged.clone();
dialog
.w(px(440.))
.title("更新就绪")
@@ -198,7 +204,7 @@ impl MqttXApp {
.on_click(move |_, window, cx| {
// 同意标记:启动时据此自动安装;
// 用户跳过对话框则不会强制升级
update::mark_install_consent(&ver);
update::mark_install_consent(&staged_consent);
window.close_dialog(cx);
}),
)