diff --git a/server/src/session/mod.rs b/server/src/session/mod.rs index 95bc2be..8eac258 100644 --- a/server/src/session/mod.rs +++ b/server/src/session/mod.rs @@ -107,6 +107,26 @@ pub struct Session { /// `true` once H.264 is live-validated, or make it per-tenant policy later. pub const DEFAULT_PREFER_H264: bool = false; +/// Per-agent opt-in tag for validating the hardware H.264 path (Task 8). +/// +/// An agent reports this tag via `AgentStatus.tags`. When present, codec +/// negotiation prefers H.264 for THAT agent only — letting us live-validate the +/// HW-H.264 pipeline on a handful of tagged test machines before flipping +/// [`DEFAULT_PREFER_H264`] globally. Clients without the tag are unaffected and +/// continue to negotiate raw (the guaranteed working path). The +/// `supports_h264 &&` guard in [`select_video_codec`] still forces raw for an +/// agent with no hardware encoder even when it carries this tag. +pub const H264_TEST_TAG: &str = "h264-test"; + +/// Whether to prefer H.264 for an agent given its reported tags (Task 8). +/// +/// Pure decision function (unit-tested): H.264 is preferred when the global +/// default prefers it OR the agent carries the per-agent test opt-in tag +/// ([`H264_TEST_TAG`]). Tag matching is case-insensitive. +pub fn prefer_h264_for(tags: &[String]) -> bool { + DEFAULT_PREFER_H264 || tags.iter().any(|t| t.eq_ignore_ascii_case(H264_TEST_TAG)) +} + /// Negotiate the video codec for a stream (Task 7). /// /// Pure decision function (unit-tested): given whether the agent advertised @@ -443,14 +463,16 @@ impl SessionManager { use prost::Message; // Negotiate the video codec for this stream (Task 7): H.264 only when the - // agent advertised hardware support AND policy prefers it. With - // DEFAULT_PREFER_H264 = false this always resolves to RAW today (H.264 is - // compile-verified only, validated on hardware in Task 8). - let codec = select_video_codec(session_data.info.supports_h264, DEFAULT_PREFER_H264); + // agent advertised hardware support AND policy prefers it. The default + // policy (DEFAULT_PREFER_H264 = false) resolves to RAW, but an agent that + // carries the per-agent H264_TEST_TAG opts itself into H.264 so the HW path + // can be live-validated (Task 8) without affecting untagged sessions. + let prefer_h264 = prefer_h264_for(&session_data.info.tags); + let codec = select_video_codec(session_data.info.supports_h264, prefer_h264); tracing::info!( "StartStream codec negotiation: agent_supports_h264={}, prefer_h264={} -> {:?}", session_data.info.supports_h264, - DEFAULT_PREFER_H264, + prefer_h264, codec ); @@ -793,6 +815,48 @@ mod tests { ); } + #[test] + fn prefer_h264_for_untagged_matches_default_policy() { + // No tags -> falls back to the global default (false today). An untagged + // session's preference must be byte-for-byte the existing behavior. + assert_eq!(prefer_h264_for(&[]), DEFAULT_PREFER_H264); + assert!(!prefer_h264_for(&[])); + // An unrelated tag must not opt the agent in. + assert!(!prefer_h264_for(&["other".to_string()])); + } + + #[test] + fn prefer_h264_for_tagged_opts_in_case_insensitively() { + assert!(prefer_h264_for(&["h264-test".to_string()])); + assert!(prefer_h264_for(&["H264-Test".to_string()])); + // Present alongside other tags still opts in. + assert!(prefer_h264_for(&[ + "other".to_string(), + "h264-test".to_string() + ])); + } + + #[test] + fn tagged_negotiation_respects_hardware_guard() { + use crate::proto::VideoCodec; + + // Tagged + HW-capable -> H.264 (the live-validation path). + assert_eq!( + select_video_codec(true, prefer_h264_for(&["h264-test".to_string()])), + VideoCodec::H264 + ); + // Tagged but NO hardware encoder -> raw (the no-HW guard still holds). + assert_eq!( + select_video_codec(false, prefer_h264_for(&["h264-test".to_string()])), + VideoCodec::Raw + ); + // Untagged + HW-capable -> raw (untagged sessions stay on the safe default). + assert_eq!( + select_video_codec(true, prefer_h264_for(&[])), + VideoCodec::Raw + ); + } + #[tokio::test] async fn agent_status_updates_h264_capability() { let mgr = SessionManager::new();