# 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 暴露到不受信任网络; - 不要复用或提交生产证书、生产私钥; - 该工具没有生产级认证、授权、持久化和多租户隔离。