306 lines
9.5 KiB
Markdown
306 lines
9.5 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` 预期由项目根 CMake 在
|
|||
|
|
`CMVR_BUILD_QUIC_TEST_GATEWAY=ON && CMVR_HAS_MSQUIC` 时加入;该开关默认跟随
|
|||
|
|
`BUILD_TESTING`。依赖目标为:
|
|||
|
|
|
|||
|
|
- `MsQuic::msquic`
|
|||
|
|
- `cmvr_es::proto`
|
|||
|
|
- `cmvr_es::quic_edge_service`
|
|||
|
|
- `Threads::Threads`
|
|||
|
|
|
|||
|
|
示例:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cmake -S . -B build \
|
|||
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|||
|
|
-DCMVR_ARCH=x86 \
|
|||
|
|
-DCMVR_ENABLE_MSQUIC_BACKEND=ON \
|
|||
|
|
-DCMVR_REQUIRE_MSQUIC=ON \
|
|||
|
|
-DCMVR_ALLOW_SYSTEM_MSQUIC=OFF \
|
|||
|
|
-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"
|
|||
|
|
enable: true
|
|||
|
|
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 暴露到不受信任网络;
|
|||
|
|
- 不要复用或提交生产证书、生产私钥;
|
|||
|
|
- 该工具没有生产级认证、授权、持久化和多租户隔离。
|