301 lines
9.3 KiB
Markdown
301 lines
9.3 KiB
Markdown
# CMVR QUIC test Gateway
|
||
|
||
`cmvr_quic_test_gateway` 是开发和验收工具,不是平台端生产 Gateway。它在本机
|
||
监听真实的 MsQuic/TLS/UDP 连接,用来验证 `cmvr_es` 的 QUIC edge v1 客户端。
|
||
|
||
测试链路:
|
||
|
||
```text
|
||
output/bin/cmvr_es
|
||
├── reliable QUIC stream ──> cmvr_quic_test_gateway
|
||
│ register / heartbeat / media metadata
|
||
└── QUIC DATAGRAM ─────────> bounded media reassembler
|
||
```
|
||
|
||
Gateway 使用生产代码中的:
|
||
|
||
- `EdgeControlEnvelope` protobuf;
|
||
- `ControlFrameEncoder` / `ControlFrameDecoder`;
|
||
- `DatagramPacketizer::decodeHeader`;
|
||
- `CMQD` v1 常量和类型。
|
||
|
||
它不会实现 WebTransport、WebRTC、HTTP API、节点数据库或浏览器播放。
|
||
|
||
## 构建
|
||
|
||
该目录的 `CMakeLists.txt` 由项目测试目录加入。MsQuic 通过根目录
|
||
`request.txt` 提供。依赖为:
|
||
|
||
- `msquic`
|
||
- `cmvr_es::proto`
|
||
- `cmvr_es::quic_edge_service`
|
||
- `Threads::Threads`
|
||
|
||
示例:
|
||
|
||
```bash
|
||
cmake -S . -B build \
|
||
-DCMAKE_BUILD_TYPE=Release \
|
||
-DCMVR_ARCH=x86 \
|
||
-DBUILD_TESTING=ON
|
||
|
||
cmake --build build --target cmvr_quic_test_gateway -j2
|
||
```
|
||
|
||
默认只生成 build-tree 测试程序,不污染部署目录。如确实希望一同安装:
|
||
|
||
```bash
|
||
cmake -S . -B build \
|
||
-DCMVR_INSTALL_QUIC_TEST_GATEWAY=ON
|
||
cmake --build build --target cmvr_quic_test_gateway -j2
|
||
cmake --install build
|
||
```
|
||
|
||
安装位置是 `output/bin/cmvr_quic_test_gateway`,可复用
|
||
`output/lib/libmsquic.so` 和现有相对 RUNPATH。
|
||
|
||
## 生成本地测试证书
|
||
|
||
QUIC 即使使用“不校验证书”的 client 模式,Server 仍必须提供 TLS 证书。
|
||
不要提交真实私钥。下面命令只在 build 目录生成 loopback 测试证书:
|
||
|
||
```bash
|
||
mkdir -p build/test/quic_gateway/certs
|
||
|
||
openssl req -x509 -newkey rsa:2048 -nodes \
|
||
-keyout build/test/quic_gateway/certs/server.key \
|
||
-out build/test/quic_gateway/certs/server.crt \
|
||
-days 7 \
|
||
-subj "/CN=127.0.0.1" \
|
||
-addext "subjectAltName=IP:127.0.0.1"
|
||
```
|
||
|
||
MsQuic/OpenSSL 当前要求这里的 PEM 私钥不带密码。
|
||
|
||
## 启动
|
||
|
||
固定端口适合人工联调。构建树中请使用 CMake 生成的启动包装器;它会补齐
|
||
仓库内 gRPC 等传递动态库的搜索路径:
|
||
|
||
```bash
|
||
build/test/quic_gateway/run_cmvr_quic_test_gateway \
|
||
--bind 127.0.0.1 \
|
||
--port 4433 \
|
||
--cert build/test/quic_gateway/certs/server.crt \
|
||
--key build/test/quic_gateway/certs/server.key \
|
||
--scenario normal \
|
||
--summary-file build/test/quic_gateway/summary.json
|
||
```
|
||
|
||
自动化测试应使用 `--port 0` 避免端口冲突。Gateway 会查询 Listener 实际端口,
|
||
在 stdout 和 `--ready-file` 写入一个 JSON 对象:
|
||
|
||
```bash
|
||
build/test/quic_gateway/run_cmvr_quic_test_gateway \
|
||
--bind 127.0.0.1 \
|
||
--port 0 \
|
||
--cert build/test/quic_gateway/certs/server.crt \
|
||
--key build/test/quic_gateway/certs/server.key \
|
||
--ready-file build/test/quic_gateway/ready.json \
|
||
--summary-file build/test/quic_gateway/summary.json \
|
||
--exit-after-heartbeats 3
|
||
```
|
||
|
||
Ready 文件示例:
|
||
|
||
```json
|
||
{"event":"ready","bind":"127.0.0.1:52319","port":52319,"alpn":"cmvr-quic-edge/1","scenario":"normal","datagram_enabled":true}
|
||
```
|
||
|
||
自动 runner 读取 `port`,将它写入临时 `quic_edge_task.pb.txt` 后再启动
|
||
`cmvr_es`。不要直接改唯一一份 `output/bin/config/`;普通安装默认会覆盖它。
|
||
只更新程序与运行库时,应在配置构建目录时设置
|
||
`-DCMVR_INSTALL_DEFAULT_RUNTIME_ASSETS=OFF`。
|
||
|
||
## cmvr_es 本机配置
|
||
|
||
第一轮只测试注册、IP 上报和心跳,保持设备和 `tracks` 关闭:
|
||
|
||
```protobuf
|
||
quic_edge {
|
||
id: "quic_edge"
|
||
server_host: "127.0.0.1"
|
||
server_port: 4433
|
||
alpn: "cmvr-quic-edge/1"
|
||
node_id: "local-quic-test"
|
||
software_version: "test"
|
||
|
||
grpc_endpoint_host: "127.0.0.1"
|
||
grpc_endpoint_port: 50052
|
||
grpc_endpoint_tls: false
|
||
include_loopback_interfaces: true
|
||
|
||
heartbeat_interval_ms: 1000
|
||
control_response_timeout_ms: 1000
|
||
|
||
tls {
|
||
allow_insecure: true
|
||
}
|
||
|
||
reconnect {
|
||
initial_delay_ms: 100
|
||
maximum_delay_ms: 1000
|
||
multiplier: 2.0
|
||
jitter_percent: 0
|
||
connect_timeout_ms: 3000
|
||
}
|
||
|
||
maximum_datagram_bytes: 1200
|
||
maximum_control_frame_bytes: 1048576
|
||
maximum_frame_bytes: 524288
|
||
datagram_send_queue_depth: 512
|
||
media_poll_interval_ms: 2
|
||
}
|
||
```
|
||
|
||
在 TaskManager 中将 `quic_edge` entry 设为 `enable: true`。然后运行一份独立的
|
||
临时配置树:
|
||
|
||
```bash
|
||
output/bin/cmvr_es /tmp/cmvr-quic-e2e/config/cmvr_es.pb.txt
|
||
```
|
||
|
||
## 场景
|
||
|
||
`--scenario` 支持:
|
||
|
||
当前 CTest 自动回归仅运行 `normal` 场景;其余场景保留为手工故障注入和验收入口,
|
||
尚未纳入自动回归。
|
||
|
||
| 名称 | 行为 | 预期 Edge 行为 |
|
||
| --- | --- | --- |
|
||
| `normal` | 接受注册并精确 ACK 心跳 | 保持同一连接 |
|
||
| `reject-registration` | 返回 `accepted=false` | 退避后重新注册 |
|
||
| `drop-heartbeat-ack` | 接收但不回复心跳 | response timeout 后重连 |
|
||
| `wrong-ack-session` | ACK 使用错误 session ID | 判定协议错误并重连 |
|
||
| `fatal-protocol-error` | 注册成功后发送 fatal `ProtocolError` | 重连 |
|
||
| `nonfatal-protocol-error` | 注册成功后发送 nonfatal `ProtocolError` | 保持连接并继续心跳 |
|
||
| `datagram-disabled` | 不协商 QUIC DATAGRAM | 注册和心跳正常,媒体不发送 |
|
||
|
||
可以用以下条件让程序自动成功退出:
|
||
|
||
- `--exit-after-registrations N`
|
||
- `--exit-after-heartbeats N`
|
||
- `--run-for-ms N`
|
||
|
||
收到 `SIGINT` 或 `SIGTERM` 时,Gateway 会停止 Listener、关闭活动连接、排空工作
|
||
队列并写 summary。
|
||
|
||
## 协议验证
|
||
|
||
每个连接独立维护:
|
||
|
||
- control framing buffer;
|
||
- Edge 和 Gateway 各自的严格递增 `message_sequence`;
|
||
- node、boot、registration session;
|
||
- media session epoch;
|
||
- track descriptor generation token;
|
||
- DATAGRAM 重组缓存。
|
||
|
||
Gateway 要求:
|
||
|
||
1. `NodeRegisterRequest` 是第一条应用消息;
|
||
2. protocol version 为 1;
|
||
3. 后续 envelope sequence 严格增加;
|
||
4. heartbeat 的 node、boot 和 session 与注册一致;
|
||
5. media session 先于 descriptor;
|
||
6. DATAGRAM 的 epoch、track、kind 和 generation token 与可靠元数据一致。
|
||
|
||
`observed_source_ip` 从 MsQuic peer address 获取,不从 Edge 上报字段复制。
|
||
Heartbeat 中存在 `device_manager` 时,Gateway 会记录 Manager 元数据以及设备
|
||
总数、启用数、禁用数、确认异常数和未知健康数;该字段是 v1 的兼容性追加项,
|
||
并在最终 summary 的 `heartbeat_devices` 数组中保留最近一次完整设备行。测试
|
||
Gateway 仍接受没有该字段的旧 Edge。当前 Edge 只发送已启用设备,因此正常情况
|
||
下禁用数为 0;该统计仍用于兼容旧发送端和发现协议违规。
|
||
summary 还会保留每类媒体最近一次 descriptor,以及已完成帧中的最大
|
||
`frame_sequence`、长度、flags、采集时间戳和 FNV-1a 64 位载荷哈希。这里的哈希
|
||
只用于测试中精确比对字节,不用于安全认证。真实 E2E 会先发送 discovery 帧并等
|
||
descriptor 到达,再发送 priming/validation 帧,从而避免 reliable stream 与
|
||
DATAGRAM 跨通道乱序造成偶发误判。
|
||
|
||
## DATAGRAM 安全边界
|
||
|
||
MsQuic callback 只复制收到的数据并进入有界队列;控制消息使用高优先级队列,
|
||
不会被媒体洪峰长期阻塞。后台线程完成 protobuf 处理和媒体重组。
|
||
|
||
重组 key 是:
|
||
|
||
```text
|
||
(session_epoch, track_id, frame_sequence)
|
||
```
|
||
|
||
接收器限制:
|
||
|
||
- 总缓存字节;
|
||
- 在途帧数;
|
||
- 单帧大小;
|
||
- 单帧最多 8192 个分片;
|
||
- 不完整帧超时;
|
||
- fragment index 冲突;
|
||
- byte range 重叠;
|
||
- 新 session epoch 清理旧分片。
|
||
|
||
相关 CLI:
|
||
|
||
```text
|
||
--max-reassembly-bytes
|
||
--max-reassembly-frames
|
||
--max-frame-bytes
|
||
--max-work-queue-bytes
|
||
--reassembly-timeout-ms
|
||
```
|
||
|
||
## 输出与判定
|
||
|
||
stdout 每行都是一个 JSON 对象或 JSON-compatible 单行事件。最终 summary 包含:
|
||
|
||
- connection/register/heartbeat 计数;
|
||
- 注册和最近一次心跳上报的网卡数量、gRPC endpoint 与对端源 IP;
|
||
- 最近一次 DeviceManager 名称/版本/描述,以及已上报设备总数、启用数、兼容性
|
||
禁用数、确认异常数、未知健康数和 `heartbeat_devices` 完整设备行;
|
||
- ACK、故障场景和协议错误计数;
|
||
- media session/descriptor/DATAGRAM 计数;
|
||
- 完整帧(含视频/音频分项)、无效包、队列丢包;
|
||
- 超时、容量淘汰和 session 清理的不完整帧。
|
||
|
||
自动化脚本至少应检查:
|
||
|
||
```text
|
||
runtime_failed == false
|
||
registrations_accepted >= 1 # normal 场景
|
||
heartbeats_received >= 1
|
||
heartbeat_acks_sent >= 1
|
||
protocol_violations == 0
|
||
```
|
||
|
||
媒体场景还应检查:
|
||
|
||
```text
|
||
media_sessions_opened >= 1
|
||
track_descriptors_received >= 1
|
||
datagrams_received >= 1
|
||
frames_completed >= 1
|
||
```
|
||
|
||
## 无设备主机的限制
|
||
|
||
关闭全部设备时,实际 `output/bin/cmvr_es` 可以完整验证 TLS、ALPN、控制 stream、
|
||
注册、IP 上报、心跳和重连,但不会产生音视频。
|
||
|
||
要在无硬件环境验证媒体 DATAGRAM,测试侧还需要 synthetic MediaSourceHub producer
|
||
或测试专用 fake camera/microphone。该 Gateway 已具备媒体接收和重组能力,但不会
|
||
伪造 Edge 发出的媒体。
|
||
|
||
## 安全说明
|
||
|
||
- 仅监听 loopback 是默认值;
|
||
- `allow_insecure` 仅限本机开发;
|
||
- 不要将测试 Gateway 暴露到不受信任网络;
|
||
- 不要复用或提交生产证书、生产私钥;
|
||
- 该工具没有生产级认证、授权、持久化和多租户隔离。
|