aerosync 0.1.0

Fast, agent-friendly file transfer with auto protocol negotiation (HTTP/QUIC), resumable chunked uploads, and CLI. Library + binary.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
# AeroSync 协议设计与功能规划

本文档覆盖协议实现细节,以及当前实现状态与后续开发规划。

---

## 目录

1. [功能实现现状]#1-功能实现现状
2. [开发路线图]#2-开发路线图
3. [P0 已实现设计参考]#3-p0-已实现设计参考
4. [P1 已实现设计参考]#4-p1-已实现设计参考
5. [P2 已实现设计参考]#5-p2-已实现设计参考
6. [协议交互时序]#6-协议交互时序
7. [多机连接实战指南]#7-多机连接实战指南

---

## 1. 功能实现现状

| # | 功能点 | 优先级 | 状态 | 说明 |
|---|--------|--------|------|------|
| 1 | SHA-256 文件完整性校验 | P0 | ✅ 已实现 | 发送方预计算、接收方校验,HTTP 单文件 + 分片均覆盖 |
| 2 | 审计日志 | P0 | ✅ 已实现 | JSONL 格式追加写入磁盘;含 IP、时间、文件名、SHA-256、协议;HTTP + QUIC 均集成 |
| 3 | TLS 证书管理 | P0 | ✅ 已实现 | 支持从 PEM 文件加载外部证书;无证书时自动生成自签名兜底;`--tls-cert`/`--tls-key` CLI 参数 |
| 4 | Auth 与传输流程集成 | P0 | ✅ 已实现 | `/upload``/upload/chunk``/upload/complete`、QUIC 均覆盖 |
| 5 | QUIC 服务端实现 | P0 | ✅ 已实现 | `start_quic_server` 完整,Quinn + rustls,含认证 |
| 6 | CLI send 命令 | P0 | ✅ 已实现 | 协议协商、SHA-256 预计算、MultiProgress、限速、预检验均完整 |
| 7 | CLI receive 命令 | P0 | ✅ 已实现 | 支持 one-shot 和持续模式;支持外部 TLS 证书 |
| 8 | 协议自动协商 | P1 | ✅ 已实现 | `/health` 探测 + `X-AeroSync` header 触发 QUIC 升级 |
| 9 | 分片上传 | P1 | ✅ 已实现 | 32MB 分片、断点续传、服务端合并校验 |
| 10 | 批量传输流水线 | P1 | ✅ 已实现 | Semaphore + FuturesUnordered,自适应并发 |
| 11 | 带宽限速 | P1 | ✅ 已实现 | Token Bucket RateLimiter;集成到分片上传;`--limit 512KB/10MB/1MB/s` |
| 12 | 传输历史持久化 | P1 | ✅ 已实现 | JSONL 追加写入 `~/.config/aerosync/history.jsonl``aerosync history` 支持过滤查询 |
| 13 | 预检验 | P1 | ✅ 已实现 | `/health` 返回 `free_bytes/total_bytes/version`;发送前自动检查磁盘空间;`--no-preflight` 跳过 |
| 14 | /health 端点 | P2 | ✅ 已实现 | 返回 `free_bytes/total_bytes/version`;含 `X-AeroSync: true` 响应头 |
| 15 | Token 持久化 | P1 | ✅ 已实现 | TOML 格式存储至 `~/.config/aerosync/tokens.toml``token generate --save/list/revoke` |
| 16 | Prometheus /metrics 端点 | P1 | ✅ 已实现 | `AtomicU64` 计数器 + 磁盘 gauge;Prometheus exposition format;`enable_metrics` 可关闭 |
| 17 | WebSocket 实时进度推送 | P1 | ✅ 已实现 | `broadcast::Sender<WsEvent>`;GET /ws;多客户端扇出;上传完成/失败自动广播 |
| 18 | 多接收目录路由 | P2 | ✅ 已实现 | 按 sender_ip / X-AeroSync-Tag / 扩展名路由;第一条匹配规则胜出;TOML 配置 |
| 19 | 配置热重载(SIGHUP) | P2 | ✅ 已实现 | `watch_config_reload()`;可热重载 auth/routing/limits;端口变更 warn + 忽略 |
| 20 | S3 Multipart Upload | P2 | ✅ 已实现 | 三步 API:initiate/upload_part/complete;超阈值自动切换;abort on failure |
| 21 | `aerosync watch` WebSocket 订阅命令 | P2 | ✅ 已实现 | CLI 原生订阅 `/ws`;pretty/json 双格式;`--filter` 事件过滤;agent 友好 |
| 22 | mDNS 局域网服务发现 | P2 | ✅ 已实现 | receiver 启动时自动广播 `_aerosync._tcp.local.``aerosync discover` 扫描局域网 |

**已实现 22 项 / 部分实现 0 项 / 未实现 0 项 — Phase 4 + Phase 5 + 补丁功能全部完成 🎉**

---

## 2. 开发路线图

### Phase 4 — 可靠性与可观测性 ✅ 已完成

| 任务 | 优先级 | 状态 | 实现文件 |
|------|--------|------|---------|
| 审计日志写入磁盘 | P0 || `aerosync-core/src/audit.rs` |
| 外部 TLS 证书加载 | P0 || `aerosync-core/src/server.rs` `TlsConfig` |
| Token 持久化到磁盘 | P1 || `aerosync-core/src/auth/store.rs` |
| 传输历史持久化 | P1 || `aerosync-core/src/history.rs` |
| 预检验(磁盘空间) | P1 || `aerosync-core/src/preflight.rs` |
| 带宽限速(Token Bucket) | P1 || `aerosync-protocols/src/ratelimit.rs` |

### Phase 5 — 生产增强 ✅ 已完成

详细设计见 [docs/phase5-plan.md](phase5-plan.md)。

| # | 任务 | 优先级 | 状态 | 实现文件 |
|---|------|--------|------|---------|
| 5.1 | Prometheus 指标端点 `/metrics` | P1 || `aerosync-core/src/metrics.rs`(新增) |
| 5.2 | WebSocket 实时传输进度推送 | P1 || `aerosync-core/src/server.rs` |
| 5.3 | 多接收目录路由(按 sender IP/tag) | P2 || `aerosync-core/src/routing.rs`(新增) |
| 5.4 | S3 Multipart Upload(大文件分片) | P2 || `aerosync-protocols/src/s3.rs` |
| 5.5 | 配置热重载(SIGHUP) | P2 || `aerosync-core/src/server.rs` |

---

## 3. P0 已实现设计参考

### 3.1 审计日志持久化 ✅

**实现**:`aerosync-core/src/audit.rs`,JSONL 格式追加写入,`Arc<Mutex<File>>` 线程安全,支持并发写入。

**核心接口**:

```rust
pub struct AuditLogger { file: Arc<Mutex<tokio::fs::File>>, path: PathBuf }

impl AuditLogger {
    pub async fn new(path: &Path) -> Result<Self>;
    pub async fn log(&self, entry: AuditEntry) -> Result<()>;
    pub async fn log_completed(&self, direction, protocol, filename, size, sha256, remote_ip);
    pub async fn log_failed(&self, direction, protocol, filename, size, remote_ip, error);
    pub async fn log_auth_failed(&self, protocol, remote_ip, reason);
    pub async fn read_all(&self) -> Result<Vec<AuditRecord>>;
    pub async fn read_recent(&self, limit: usize) -> Result<Vec<AuditRecord>>;
}
```

**日志文件位置**:`ServerConfig.audit_log: Option<PathBuf>`(由调用方指定)

**日志格式(JSONL)**:

```jsonl
{"timestamp":1775903419,"event":{"type":"transfer_completed"},"filename":"data.bin","size":157286400,"sha256":"204be31...","remote_ip":"192.168.1.5","direction":"Receive","result":"Ok","protocol":"http"}
{"timestamp":1775903500,"event":{"type":"auth_failed"},"filename":"","size":0,"remote_ip":"10.0.0.3","direction":"Receive","result":{"Err":"Invalid token"},"protocol":"http"}
```

**集成点**:
- `server.rs handle_file_upload` / `handle_chunk_complete` — 记录接收事件
- `transfer.rs transfer_worker` — 记录发送事件
- 所有 auth 失败路径 — 记录 `AuthFailed`

---

### 3.2 外部 TLS 证书加载 ✅

**实现**:`ServerConfig.tls: Option<TlsConfig>`,`rustls-pemfile` 加载 PEM 文件;`--tls-cert`/`--tls-key` CLI 参数;无证书时自动生成自签名兜底。

**核心接口**:

```rust
// aerosync-core/src/server.rs
pub struct TlsConfig {
    pub cert_path: PathBuf,   // PEM 证书文件路径
    pub key_path: PathBuf,    // PEM 私钥文件路径
}

// configure_quic_server(tls: Option<&TlsConfig>) — 有证书时加载,无则自签名
// load_tls_from_pem(cert_path, key_path) — 支持 PKCS8 和 RSA 两种私钥格式
```

**CLI 用法**:

```bash
aerosync receive --tls-cert /path/to/cert.pem --tls-key /path/to/key.pem
```

---

## 4. P1 已实现设计参考

### 4.1 Token 持久化 ✅

**实现**:`aerosync-core/src/auth/store.rs`,TOML 格式存储到 `~/.config/aerosync/tokens.toml`;`token generate --save/list/revoke` 子命令。

**核心接口**:

```rust
pub struct TokenStore { path: PathBuf }

impl TokenStore {
    pub fn new(path: &Path) -> Self;
    pub fn default_path() -> PathBuf;  // ~/.config/aerosync/tokens.toml
    pub fn save(&self, token: &str, label: Option<&str>, expires_at: u64) -> Result<()>;
    pub fn list_all(&self) -> Result<Vec<StoredToken>>;
    pub fn list_valid(&self) -> Result<Vec<StoredToken>>;
    pub fn find_by_prefix(&self, prefix: &str) -> Result<Option<StoredToken>>;
    pub fn revoke(&self, token: &str) -> Result<bool>;
    pub fn prune(&self) -> Result<usize>;  // 清理过期/已撤销
}
```

**CLI 用法**:

```bash
aerosync token generate --save            # 生成并保存到 tokens.toml
aerosync token generate --save --label "prod-server-1"
aerosync token list                       # 列出所有已保存 token
aerosync token revoke <token-prefix>      # 按前缀撤销
```

---

### 4.2 带宽限速 ✅

**实现**:`aerosync-protocols/src/ratelimit.rs`,Token Bucket 算法;集成到 `HttpTransfer.upload_chunked` 分片循环;`--limit 512KB/10MB/1MB/s` CLI 参数;`parse_limit()` 支持多种单位格式。

**核心接口**:

```rust
pub struct RateLimiter { inner: Arc<Mutex<RateLimiterInner>> }

impl RateLimiter {
    pub fn new(rate_bytes_per_sec: u64) -> Self;  // 0 = 不限速
    pub fn unlimited() -> Self;
    pub async fn consume(&self, bytes: u64);      // 令牌不足时异步等待
}

/// 解析限速字符串:"512KB" / "10MB" / "1MB/s" / "100"(默认 KB/s)
pub fn parse_limit(s: &str) -> Option<u64>;
```

**集成**:`HttpConfig.upload_limit_bps: u64`(0 = 不限速),每个分片上传前调用 `rate_limiter.consume(chunk_size).await`

**CLI 用法**:

```bash
aerosync send ./large-file.bin 192.168.1.10:7788 --limit 10MB
aerosync send ./dir/ host:7788 --limit 512KB -r
```

---

### 4.3 传输历史持久化 ✅

**实现**:`aerosync-core/src/history.rs`,JSONL 格式追加到 `~/.config/aerosync/history.jsonl`;`HistoryQuery` 支持按方向/协议/成功状态过滤;`aerosync history --sent/--received/--success-only/--limit` 子命令。

**核心接口**:

```rust
pub struct HistoryStore { path: PathBuf, file: Arc<Mutex<tokio::fs::File>> }

impl HistoryStore {
    pub async fn new(path: &Path) -> Result<Self>;
    pub fn default_path() -> PathBuf;  // ~/.config/aerosync/history.jsonl
    pub async fn append(&self, entry: HistoryEntry) -> Result<()>;
    pub async fn append_silent(&self, entry: HistoryEntry);  // fire-and-forget
    pub async fn read_all(&self) -> Result<Vec<HistoryEntry>>;
    pub async fn query(&self, q: &HistoryQuery) -> Result<Vec<HistoryEntry>>;
    pub async fn recent(&self, limit: usize) -> Result<Vec<HistoryEntry>>;
}

pub struct HistoryQuery {
    pub direction: Option<String>,   // "send" / "receive"
    pub protocol: Option<String>,    // "http" / "quic" / ...
    pub success_only: bool,
    pub limit: usize,                // 0 = 不限
}
```

**CLI 用法**:

```bash
aerosync history                    # 最近 20 条(默认)
aerosync history --limit 50
aerosync history --sent             # 只看发送记录
aerosync history --success-only    # 只看成功记录
```

---

### 4.4 预检验(Preflight)✅

**实现**:`aerosync-core/src/preflight.rs`,`preflight_check()` 探测 `/health` 并验证磁盘空间;`free_bytes == 0` 时跳过检查(兼容旧版);`--no-preflight` 标志完全跳过;`/health` 扩展返回 `free_bytes/total_bytes/version`(`libc::statvfs` 跨平台实现)。

**`/health` 响应格式**:

```json
{
  "status": "ok",
  "received_files": 12,
  "free_bytes": 107374182400,
  "total_bytes": 536870912000,
  "version": "0.2.0"
}
```

**核心接口**:

```rust
/// 探测接收端健康状态(返回磁盘信息和版本号)
pub async fn probe_receiver(http_base: &str) -> Result<PreflightResult>;

/// 验证磁盘空间是否足够;free_bytes==0 时视为跳过
pub async fn preflight_check(
    http_base: &str,
    total_bytes: u64,
) -> Result<PreflightResult, PreflightError>;
```

**CLI 用法**:`aerosync send` 默认自动执行预检验,`--no-preflight` 跳过。

---

## 5. P2 已实现设计参考

### 5.1 Prometheus /metrics 端点 ✅

**实现**:`aerosync-core/src/metrics.rs`,`Arc<Metrics>` 全局单例,`AtomicU64` 零锁计数器,`render()` 手动生成 exposition format(无外部 crate 依赖)。

**核心接口**:

```rust
pub struct Metrics {
    pub files_received_total: AtomicU64,
    pub bytes_received_total: AtomicU64,
    pub upload_errors_total: AtomicU64,
    pub ws_connections_total: AtomicU64,
    // active_ws_connections: AtomicU64 (gauge)
}

impl Metrics {
    pub fn new() -> Arc<Self>;
    pub fn inc_files_received(&self);
    pub fn add_bytes_received(&self, n: u64);
    pub fn inc_upload_errors(&self);
    pub fn inc_ws_connections(&self);
    pub fn dec_ws_connections(&self);
    pub fn render(&self, free_bytes: u64, total_bytes: u64) -> String;
}
```

**端点**:`GET /metrics`,`Content-Type: text/plain; version=0.0.4`,通过 `ServerConfig.enable_metrics: bool` 控制开关。

---

### 5.2 WebSocket 实时进度推送 ✅

**实现**:`WsEvent` JSON 枚举通过 `tokio::sync::broadcast` 广播给所有连接的客户端;客户端断连 fire-and-forget;`Lagged` 错误打印 warn 后继续。

**核心接口**:

```rust
#[derive(Serialize)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum WsEvent {
    TransferStarted { filename: String, size: u64, sender_ip: String },
    Progress        { filename: String, bytes: u64, total: u64 },
    Completed       { filename: String, size: u64, sha256: String },
    Failed          { filename: String, reason: String },
}

pub type WsBroadcast = broadcast::Sender<WsEvent>;
```

**端点**:`GET /ws`(WebSocket 升级),通过 `ServerConfig.enable_ws` 控制开关,`ws_event_buffer` 配置广播缓冲区。

---

### 5.3 多接收目录路由 ✅

**实现**:`aerosync-core/src/routing.rs`,`Router::resolve()` 按优先级迭代规则列表,第一条匹配胜出,无匹配回退 `receive_directory`。

**核心接口**:

```rust
pub struct RoutingRule {
    pub name: String,
    pub destination: PathBuf,
    pub tag: Option<String>,        // X-AeroSync-Tag header
    pub sender_ip: Option<String>,  // exact IP match
    pub extension: Option<String>,  // case-insensitive, without dot
}

pub struct Router { config: RouterConfig, default_dir: PathBuf }

impl Router {
    pub fn resolve(&self, sender_ip: &str, tag: Option<&str>, filename: &str) -> PathBuf;
}
```

**配置**:`ServerConfig.routing: Option<RouterConfig>`(TOML 可序列化)。

---

### 5.4 S3 Multipart Upload ✅

**实现**:`aerosync-protocols/src/s3.rs`,三步 API;`upload_auto()` 按 `multipart_threshold` 自动选择路径;part 失败时自动调用 `abort_multipart()` 清理。

**核心接口**:

```rust
impl S3Transfer {
    pub async fn initiate_multipart(&self, bucket, key) -> Result<String>;                       // → UploadId
    pub async fn upload_part(&self, bucket, key, upload_id, part_number, data) -> Result<String>; // → ETag
    pub async fn complete_multipart(&self, bucket, key, upload_id, parts) -> Result<()>;
    pub async fn abort_multipart(&self, bucket, key, upload_id) -> Result<()>;
    pub async fn upload_auto(&self, file_path, url, progress_tx) -> Result<()>;                  // 自动选路
}
```

**配置**:`S3Config.multipart_threshold`(默认 100MB)、`S3Config.part_size`(默认 16MB)。

---

### 5.5 配置热重载(SIGHUP)✅

**实现**:`FileReceiver::watch_config_reload(config_path)` 启动后台 task,监听 `SIGHUP`(Unix only),重新读取 TOML 配置文件并只更新可热重载字段。

**可热重载字段**:`max_file_size`、`allow_overwrite`、`auth`、`routing`、`audit_log`

**不可热重载字段**:`http_port`、`quic_port`、`bind_address`(变更时打印 warn 并忽略,需重启生效)

**用法**:

```bash
# 修改配置文件后发送 SIGHUP,无需重启服务
kill -HUP $(pidof aerosync)
```

---

### 5.6 `aerosync watch` WebSocket 订阅命令 ✅

**实现**:`src/main.rs`,`tokio-tungstenite` 客户端,`build_ws_url()` 归一化输入地址,连接 `ws://host:port/ws` 后持续接收 `WsEvent` JSON 消息。`watch_once()` 封装单次连接逻辑,`cmd_watch()` 外层循环实现指数退避自动重连。

**CLI 参数**:

| 参数 | 默认值 | 说明 |
|------|--------|------|
| `<host>` | `localhost:7788` | 接收端地址(`host:port` 或完整 `ws://` URL) |
| `--format` | `pretty` | `pretty`(人类可读)或 `json`(机器可读原始 JSON) |
| `--filter <event>` || 只输出含指定事件类型的消息 |
| `--reconnect` | false | 断连后自动重连(默认关闭) |
| `--max-retries <n>` | `0` | 最大重连次数,`0` 表示无限重连(需配合 `--reconnect`|
| `--retry-delay <s>` | `2` | 初始等待秒数,每次失败后翻倍,上限 60 秒 |

**重连退避策略**:

| attempt | delay(retry-delay=2) |
|---------|------------------------|
| 1 | 2s |
| 2 | 4s |
| 3 | 8s |
| 4 | 16s |
| 5 | 32s |
| 6+ | 60s(上限) |

服务端主动发送 Close frame 时**不触发重连**(视为正常关闭)。

**输出规则(pretty 模式)**:

| 事件 | 输出目标 | 说明 |
|------|----------|------|
| `completed` | **stdout** | agent 只需捕获 stdout 即可感知文件到达 |
| `transfer_started` / `failed` | stderr | 状态信息,不干扰管道 |
| `progress` | 静默 | 默认不输出,避免刷屏 |

**使用示例**:

```bash
# 1. 基本用法 — 连接本地接收端,pretty 格式输出
aerosync watch
# 等同于:
aerosync watch localhost:7788 --format pretty

# 2. 连接远端接收端
aerosync watch 10.0.0.5:7788

# 3. 使用完整 ws:// URL(自定义路径)
aerosync watch ws://10.0.0.5:7788/ws

# 4. 只显示 completed 事件(过滤掉 started/progress/failed)
aerosync watch --filter completed

# 5. agent 模式 — 机器可读 JSON,stderr 丢弃(不刷屏)
#    stdout 每行输出一个完整 JSON 对象
aerosync watch --format json 2>/dev/null
# 输出示例:
# {"event":"completed","filename":"report.pdf","size":2097152,"sha256":"a1b2c3d4..."}

# 6. agent 脚本:管道解析文件到达事件
aerosync watch --format json 2>/dev/null | while IFS= read -r line; do
  filename=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['filename'])")
  echo "[AGENT] File arrived: $filename"
done

# 7. agent 脚本(jq 版):提取 completed 事件中的 sha256
aerosync watch --format json 2>/dev/null \
  | jq -r 'select(.event=="completed") | "\(.filename) sha256=\(.sha256)"'

# 8. 只监听失败事件(用于告警)
aerosync watch --filter failed --format json 2>/dev/null \
  | jq -r '"ALERT: upload failed for \(.filename): \(.reason)"'

# 9. 自动重连(断网后无限重试,初始等待 2s,最多等 60s)
aerosync watch --reconnect

# 10. 限制重连次数(最多重试 5 次,初始等待 3s)
aerosync watch --reconnect --max-retries 5 --retry-delay 3

# 11. agent 场景:重连 + json 模式(服务重启后自动恢复订阅)
aerosync watch --reconnect --format json 2>/dev/null \
  | jq -r 'select(.event=="completed") | .filename'
```

**与 Prometheus 指标的组合使用**:

```bash
# 终端 1:实时监听文件事件
aerosync watch --filter completed

# 终端 2:定期采集指标
watch -n5 'curl -s localhost:7788/metrics | grep aerosync_files_received_total'
```

**注意事项**:
- `aerosync watch` 会持续运行直到 `Ctrl+C` 或服务端关闭连接
- 若接收端未启用 WebSocket(`[ws] enabled = false`),连接会被拒绝并返回 404
- 事件缓冲区满(默认 256 条)时,滞后的客户端会收到 `WebSocket error: lagged` 警告,不影响接收端正常运行

---

## 6. 协议交互时序

### 6.1 QUIC 自动协商流程

```
Client                           Server (port 7788)
  │                                    │
  │  GET http://{host}:7788/health     │
  │ ─────────────────────────────────► │
  │                                    │
  │  200 OK                            │
  │  X-AeroSync: true                  │
  │ ◄───────────────────────────────── │
  │                                    │
  │  (升级到 QUIC, port = 7788+1)      │
  │                                    │
  │  QUIC connect → {host}:7789        │
  │ ─────────────────────────────────► │
  │                                    │
  │  UPLOAD:{filename}:{size}:{token}  │
  │ ─────────────────────────────────► │
  │                                    │
  │  stream bytes (流式传输)            │
  │ ─────────────────────────────────► │
  │                                    │
  │  OK:{sha256}                       │
  │ ◄───────────────────────────────── │
```

降级条件:
- `/health` 超时(2s)
- 响应头无 `X-AeroSync: true`
- 目标 URL 已有显式协议前缀(`http://``s3://``ftp://`
### 6.2 分片上传流程

```
Client                                    Server
  │                                          │
  │  POST /upload/chunk                      │
  │  ?task_id=UUID&chunk_index=0             │
  │  &total_chunks=5&filename=file.bin       │
  │  body: [32MB bytes]                      │
  │ ───────────────────────────────────────► │
  │  200 OK {"received":0}                   │  写入 .aerosync/tmp/{uuid}/00000000
  │ ◄─────────────────────────────────────── │
  │                                          │
  │  POST /upload/chunk?chunk_index=1 ...    │
  │ ───────────────────────────────────────► │
  │  200 OK {"received":1}                   │  写入 .aerosync/tmp/{uuid}/00000001
  │ ◄─────────────────────────────────────── │
  │                                          │
  │  ... (中途可中断,状态持久化) ...          │
  │                                          │
  │  POST /upload/complete                   │
  │  ?task_id=UUID&filename=file.bin         │
  │  &total_chunks=5&sha256=204be31...       │
  │ ───────────────────────────────────────► │
  │                                          │  顺序拼接 00000000~00000004
  │                                          │  校验 SHA-256
  │  200 OK {"saved":"file.bin","size":...}  │  删除 tmp/{uuid}/
  │ ◄─────────────────────────────────────── │
```

断点续传恢复:
- 客户端读取 `.aerosync/{task_id}.json`
- `pending_chunks()` 跳过 `completed_chunks` 中的序号
- 直接从剩余分片继续,不重传已完成分片

### 6.3 Auth 验证流程(HTTP)

```
Client                                    Server
  │                                          │
  │  POST /upload                            │
  │  Authorization: Bearer <token>           │
  │  X-File-Hash: sha256hex                  │
  │ ───────────────────────────────────────► │
  │                                          │  AuthMiddleware::authenticate_http_request()
  │                                          │  ├─ 解析 Bearer token
  │                                          │  ├─ HMAC-SHA256 验签
  │                                          │  ├─ 检查过期时间
  │                                          │  └─ 验证通过 → 处理上传
  │  401 Unauthorized(验证失败)             │
  │  or 200 OK(验证通过)                    │
  │ ◄─────────────────────────────────────── │
```

### 6.4 S3 上传流程

```
Client                           S3 / MinIO
  │                                    │
  │  PUT /{bucket}/{key}               │
  │  Authorization: AWS4-HMAC-SHA256   │  (AWS S3)
  │  or Authorization: Bearer token    │  (MinIO)
  │  x-amz-date: 20260411T000000Z      │
  │  Content-Length: {size}            │
  │  body: file bytes                  │
  │ ─────────────────────────────────► │
  │  200 OK                            │
  │ ◄───────────────────────────────── │
```

URL 格式:`s3://bucket-name/path/to/key`

不支持分片续传(S3 Multipart Upload 已实现,见 §5.4),大文件超过 `multipart_threshold`(默认 100MB)自动切换为分片上传。

---

*最后更新:2026-04-12(mDNS 服务发现 + 多机连接指南完成,累计 231 个测试,0 失败)*

---

## 7. 多机连接实战指南

本章描述两台或多台机器之间真实使用 AeroSync 的完整流程:**服务发现 → 能力协商 → 连接建立 → 传输 → 事件订阅**。

---

### 7.1 服务发现

#### 方式一:mDNS 自动发现(局域网推荐)

receiver 启动后自动在局域网广播 `_aerosync._tcp.local.`,sender 无需手动输入 IP。

**实现**:`aerosync-core/src/discovery.rs`,`AeroSyncMdns::register()` 在 `FileReceiver::start()` 中自动调用。

**mDNS TXT 记录**:

| 字段 || 说明 |
|------|-----|------|
| `version` | `0.2.0` | AeroSync 版本号 |
| `ws` | `true/false` | WebSocket 是否启用 |
| `auth` | `true/false` | 是否需要认证 token |

**发现 receiver:**

```bash
# 扫描局域网,等待 3 秒(默认)
aerosync discover

# 扫描 5 秒,JSON 格式(适合脚本)
aerosync discover --timeout 5 --json
```

**示例输出:**

```
Scanning for AeroSync receivers on local network (3s)…

Found 2 receiver(s):

NAME                 ADDRESS                VERSION    WS     AUTH
--------------------------------------------------------------------
machine-b.local      192.168.1.20:7788      0.2.0      yes    no
gpu-server.local     10.0.0.5:7788          0.2.0      yes    yes
```

**JSON 模式(脚本):**

```bash
# 取第一个 receiver 的地址
ADDR=$(aerosync discover --json | head -1 | python3 -c "import sys,json; print(json.load(sys.stdin)['addr'])")
aerosync send ./file.bin "$ADDR"
```

#### 方式二:手动指定 IP(跨网络 / 无 mDNS 场景)

```bash
# 直接用 host:port,自动协商协议
aerosync send ./file.bin 192.168.1.20:7788

# 强制 HTTP(NAT/防火墙限制 UDP 时)
aerosync send ./file.bin http://192.168.1.20:7788/upload
```

---

### 7.2 能力协商

连接建立前有两步自动协商:

#### 第一步:协议协商(`src/main.rs: negotiate_protocol()`

```
sender 输入 host:port(无协议前缀)
  │
  ├─► GET http://host:port/health  (2s timeout)
  │         Response Header: X-AeroSync: true ?
  │
  ├─ 是 AeroSync ──► 升级 QUIC: quic://host:(port+1)/upload
  └─ 非 AeroSync / 超时 ──► 降级 HTTP: http://host:port/upload
```

输入已有协议前缀(`http://`、`quic://`、`s3://`、`ftp://`)时**跳过协商**,直接使用指定协议。

#### 第二步:Preflight 磁盘检查(`aerosync-core/src/preflight.rs`

```
GET http://host:port/health
← { version, free_bytes, total_bytes, received_files }

if free_bytes < total_transfer_size:
    abort("Insufficient disk space on receiver")
else:
    proceed with transfer
```

`--no-preflight` 跳过此检查。`free_bytes == 0` 时(旧版 receiver)视为空间充足,不中断。

---

### 7.3 连接建立全流程时序

```
Machine A (sender)                     Machine B (receiver, IP=192.168.1.20)
      │                                          │
      │  [mDNS] _aerosync._tcp.local broadcast   │ ← receiver 启动时自动广播
      │ ◄──────────────────────────────────────  │
      │                                          │
      │  aerosync discover                       │
      │  → 发现 192.168.1.20:7788               │
      │                                          │
      │  GET /health (协议探测)                  │
      │ ────────────────────────────────────────►│
      │  200 OK  X-AeroSync: true                │
      │ ◄──────────────────────────────────────  │
      │                                          │
      │  (升级到 QUIC, port 7789)                │
      │                                          │
      │  GET /health (preflight 磁盘检查)        │
      │ ────────────────────────────────────────►│
      │  { free_bytes: 500GB, version: "0.2.0" } │
      │ ◄──────────────────────────────────────  │
      │                                          │
      │  QUIC connect → 192.168.1.20:7789        │
      │ ────────────────────────────────────────►│
      │  TLS 1.3 handshake (自签名证书)           │
      │ ◄──────────────────────────────────────► │
      │                                          │
      │  UPLOAD:file.bin:1073741824:Bearer_token │
      │ ────────────────────────────────────────►│
      │                                          │
      │  stream bytes (多并发流)                  │
      │ ════════════════════════════════════════►│
      │                                          │
      │  OK:sha256hex                            │
      │ ◄──────────────────────────────────────  │
      │                                          │
      │  (可选) GET /ws (WebSocket 订阅)         │
      │ ────────────────────────────────────────►│
      │  { event: "completed", filename: ... }   │
      │ ◄──────────────────────────────────────  │
```

---

### 7.4 认证

#### 配置认证

```bash
# receiver 端
aerosync receive --auth-token my_secret_token

# sender 端
aerosync send ./file.bin 192.168.1.20:7788 --token my_secret_token
```

#### 认证流程(HTTP)

```
HTTP Header: Authorization: Bearer my_secret_token
  → AuthMiddleware::extract_token_from_header()
  → AuthManager::authenticate(token, client_ip)
  → 失败 → 401 Unauthorized,记录审计日志
  → 成功 → 继续处理上传
```

支持格式:
- `Authorization: Bearer <token>`
- `X-Auth-Token: <token>`(直接 token)

#### Token 管理

```bash
# 生成长期 token 并保存
aerosync token generate --save --label "machine-b-prod"

# 列出所有 token
aerosync token list

# 撤销 token
aerosync token revoke <token-prefix>
```

---

### 7.5 跨网络场景(NAT / 防火墙)

AeroSync **不内置 NAT 穿透**,跨网络需借助外部手段:

| 场景 | 推荐方案 | 说明 |
|------|----------|------|
| 同局域网 | mDNS 自动发现 | 零配置,`aerosync discover` 直接找到 |
| receiver 有公网 IP | 直接连接 | 防火墙放行 TCP 7788(HTTP)和 UDP 7789(QUIC)|
| 双方都在 NAT 后 | Tailscale / WireGuard | 建立 VPN 后用虚拟 IP 连接 |
| 企业内网 | SSH 反向隧道 | `ssh -R 7788:localhost:7788 jump-server` |
| UDP 被封锁(QUIC 不通) | 强制 HTTP |`http://host:7788/upload` 显式 URL |
| Docker / 容器 | 映射端口 | `-p 7788:7788 -p 7789:7789/udp` |

#### QUIC 端口说明

| 协议 | 端口 | 类型 | 用途 |
|------|------|------|------|
| HTTP | 7788(默认) | TCP | 文件上传、/health、/ws、/metrics |
| QUIC | 7789(默认) | UDP | 高性能文件传输 |

防火墙规则(以 iptables 为例):

```bash
# receiver 机器上
iptables -A INPUT -p tcp --dport 7788 -j ACCEPT
iptables -A INPUT -p udp --dport 7789 -j ACCEPT
```

#### 仅 HTTP 模式(禁用 QUIC)

```bash
# receiver 端禁用 QUIC
aerosync receive --http-only

# sender 端强制 HTTP
aerosync send ./file.bin http://host:7788/upload
```

---

### 7.6 多机 Agent 协作

两个 agent 通过 AeroSync 通信的完整操作流程:

**机器 B(receiver agent):**

```bash
# 方式一:Python agent(自动管理 aerosync receive 子进程)
python examples/receiver_agent.py \
  --save-dir /data/inbox \
  --host 0.0.0.0:7788

# 方式二:直接启动 aerosync receive
aerosync receive --save-to /data/inbox --port 7788
# 另一终端订阅事件
aerosync watch --reconnect --format json 2>/dev/null \
  | jq -r 'select(.event=="completed") | .filename'
```

**机器 A(sender agent):**

```bash
# 自动发现局域网内的 receiver
aerosync discover

# Python agent(自动监控目录 + 订阅 WS 回传)
python examples/sender_agent.py \
  --watch-dir /data/outbox \
  --host 192.168.1.20:7788 \
  --results-dir /data/results

# 或直接发送
aerosync send ./payload.bin 192.168.1.20:7788
```

**跨机 WS 订阅(sender 监听 receiver 事件):**

```bash
# 机器 A 订阅机器 B 的事件流
aerosync watch 192.168.1.20:7788 --reconnect --format json 2>/dev/null \
  | jq -r 'select(.event=="completed") | "File arrived: \(.filename) (\(.size) bytes)"'
```

---

### 7.7 `aerosync discover` 命令参考

**实现**:`src/main.rs: cmd_discover()`,调用 `AeroSyncMdns::discover()` 扫描局域网。

| 参数 | 默认值 | 说明 |
|------|--------|------|
| `--timeout <s>` | `3` | 扫描等待时间(秒) |
| `--json` | false | JSON 格式输出(每行一个 receiver) |

**JSON 输出字段**:

```json
{
  "name":          "machine-b.local",
  "host":          "192.168.1.20",
  "port":          7788,
  "addr":          "192.168.1.20:7788",
  "version":       "0.2.0",
  "ws_enabled":    true,
  "auth_required": false
}
```

**脚本集成示例**:

```bash
# 自动发现并发送到第一个无认证的 receiver
ADDR=$(aerosync discover --json \
  | jq -r 'select(.auth_required == false) | .addr' \
  | head -1)
[ -n "$ADDR" ] && aerosync send ./file.bin "$ADDR"

# 发现所有支持 WS 的 receiver 并订阅
aerosync discover --json \
  | jq -r 'select(.ws_enabled == true) | .addr' \
  | while read addr; do
      aerosync watch "$addr" --reconnect &
    done
```

---

### 7.8 mDNS 服务发现实现参考

**实现**:`aerosync-core/src/discovery.rs`

**核心接口**:

```rust
/// 广播句柄 — 保持存活则持续广播,drop 时自动注销
pub struct MdnsHandle { ... }

pub struct AeroSyncMdns;

impl AeroSyncMdns {
    /// receiver 端:在局域网广播自身(FileReceiver::start() 自动调用)
    pub fn register(
        instance_name: &str,
        port: u16,
        version: &str,
        ws_enabled: bool,
        auth_required: bool,
    ) -> Result<MdnsHandle, mdns_sd::Error>;

    /// sender 端 / discover 命令:扫描局域网内所有 AeroSync receiver
    pub async fn discover(timeout: Duration) -> Vec<AeroSyncPeer>;
}

pub struct AeroSyncPeer {
    pub name:          String,
    pub host:          String,   // IPv4 优先
    pub port:          u16,
    pub version:       Option<String>,
    pub ws_enabled:    bool,
    pub auth_required: bool,
}

impl AeroSyncPeer {
    pub fn addr(&self) -> String;  // "host:port"
}
```

**集成点**:
- `FileReceiver::start()` → 自动调用 `AeroSyncMdns::register()`
- `FileReceiver::stop()` → 自动 drop `MdnsHandle`,注销广播
- `cmd_discover()` → 调用 `AeroSyncMdns::discover()`

**mDNS 不可用时的行为**:广播失败只打印 `warn` 日志,不影响 receiver 正常工作(non-fatal)。