feat(agent,server): v2 secure-session-core Task 7 - HW H.264 + negotiated raw fallback
All checks were successful
All checks were successful
SPEC-002 Phase 1 Task 7 (the last), code-reviewed APPROVED, locally verified (cargo fmt + clippy -D warnings exit 0 + cargo test --workspace 89 pass + build). - Encoder trait + factory: RawEncoder (salvaged, UNCHANGED) and H264Encoder, selected by negotiation; factory falls back to raw on H.264 init failure. - Negotiation: agent advertises supports_h264 (MFTEnumEx HW probe, cached) in AgentStatus; server picks the codec via select_video_codec(supports, prefer) and stamps StartStream.video_codec; agent re-guards on local HW. Policy constant DEFAULT_PREFER_H264 = false, so RAW is negotiated for every session today - H.264 stays dormant until live hardware validation (Task 8). - MF H.264 encoder (h264.rs, FIRST-CUT / compile-verified-only): HW encoder MFT, BGRA->NV12 (color.rs, unit-tested), sync drain, fall-back-to-raw on any failure. - Viewer H.264 decoder (decoder.rs, FIRST-CUT): MF decoder on a dedicated COM thread; drops+logs on failure, raw render path untouched. - proto additive: VideoCodec enum, StartStream.video_codec=3, SessionResponse.video_codec=5, AgentStatus.supports_h264=11. - Raw+Zstd path byte-for-byte unchanged; remains the guaranteed default/fallback. Review confirmed unsafe impl Send for H264Encoder is sound (single-owned &mut on the block_on thread; session future never spawned) and every MF failure degrades to raw. H.264 is NOT claimed functional - compile/clippy/build-verified only; live validation + force-IDR + the no-spawn-invariant doc are Task 8 go-live gates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,10 @@ pub struct SessionManager {
|
||||
input: Option<InputController>,
|
||||
// Streaming state
|
||||
current_viewer_id: Option<String>,
|
||||
// Codec negotiated by the server for the current stream (Task 7). Set from
|
||||
// StartStream.video_codec; the encoder is built from it (guarded by the
|
||||
// agent's own hardware capability, with raw as the safe fallback).
|
||||
negotiated_codec: crate::proto::VideoCodec,
|
||||
// System info for status reports
|
||||
hostname: String,
|
||||
is_elevated: bool,
|
||||
@@ -87,6 +91,8 @@ impl SessionManager {
|
||||
encoder: None,
|
||||
input: None,
|
||||
current_viewer_id: None,
|
||||
// Default to RAW until the server negotiates otherwise (StartStream).
|
||||
negotiated_codec: crate::proto::VideoCodec::Raw,
|
||||
hostname,
|
||||
is_elevated,
|
||||
start_time: Instant::now(),
|
||||
@@ -168,14 +174,20 @@ impl SessionManager {
|
||||
self.capturer = Some(capturer);
|
||||
tracing::info!("Capturer created successfully");
|
||||
|
||||
// Create encoder with panic protection
|
||||
// Create encoder from the NEGOTIATED codec (Task 7), guarded by the
|
||||
// agent's own hardware capability. `create_encoder_for` selects the H.264
|
||||
// encoder only if it can actually be constructed, otherwise it returns a
|
||||
// working raw encoder — so this never breaks the session.
|
||||
let chosen =
|
||||
encoder::select_codec(self.negotiated_codec, encoder::supports_hardware_h264());
|
||||
tracing::debug!(
|
||||
"Creating encoder (codec={}, quality={})...",
|
||||
self.config.encoding.codec,
|
||||
"Creating encoder (negotiated={:?}, chosen={:?}, quality={})...",
|
||||
self.negotiated_codec,
|
||||
chosen,
|
||||
self.config.encoding.quality
|
||||
);
|
||||
let encoder = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
encoder::create_encoder(&self.config.encoding.codec, self.config.encoding.quality)
|
||||
encoder::create_encoder_for(chosen, self.config.encoding.quality)
|
||||
})) {
|
||||
Ok(result) => result?,
|
||||
Err(e) => {
|
||||
@@ -232,6 +244,9 @@ impl SessionManager {
|
||||
organization: self.config.company.clone().unwrap_or_default(),
|
||||
site: self.config.site.clone().unwrap_or_default(),
|
||||
tags: self.config.tags.clone(),
|
||||
// Advertise hardware H.264 capability so the server can negotiate the
|
||||
// codec (Task 7). Detected once and cached by the encoder module.
|
||||
supports_h264: encoder::supports_hardware_h264(),
|
||||
};
|
||||
|
||||
let msg = Message {
|
||||
@@ -336,6 +351,15 @@ impl SessionManager {
|
||||
match payload {
|
||||
message::Payload::StartStream(start) => {
|
||||
tracing::info!("StartStream received from viewer: {}", start.viewer_id);
|
||||
// Apply the server-negotiated codec (Task 7) BEFORE
|
||||
// building the encoder. An older server that omits the
|
||||
// field sends 0 = VIDEO_CODEC_RAW, preserving the raw
|
||||
// default. `select_codec` (in init_streaming) re-guards
|
||||
// against missing hardware.
|
||||
self.negotiated_codec =
|
||||
crate::proto::VideoCodec::try_from(start.video_codec)
|
||||
.unwrap_or(crate::proto::VideoCodec::Raw);
|
||||
tracing::info!("Server negotiated codec: {:?}", self.negotiated_codec);
|
||||
if let Err(e) = self.init_streaming() {
|
||||
tracing::error!("Failed to init streaming: {}", e);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user