From 6b98f067c4d791a3c1892003120a2cb21837bf2e Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 19 May 2026 13:09:08 -0300 Subject: [PATCH] fix: reject MJPEG during HEVC UVC handoff --- Cargo.lock | 6 +++--- client/Cargo.toml | 2 +- common/Cargo.toml | 2 +- server/Cargo.toml | 2 +- server/src/video_sinks/webcam_sink.rs | 1 + .../src/video_sinks/webcam_sink/constructor.rs | 2 ++ .../video_sinks/webcam_sink/frame_handoff.rs | 18 +++++++++++++++++- 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bcf692..a8285e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.26.1" +version = "0.26.2" dependencies = [ "anyhow", "async-stream", @@ -1692,7 +1692,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.26.1" +version = "0.26.2" dependencies = [ "anyhow", "base64", @@ -1704,7 +1704,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.26.1" +version = "0.26.2" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index 4724ffc..1affe8a 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.26.1" +version = "0.26.2" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index 250f9ff..29d832b 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.26.1" +version = "0.26.2" edition = "2024" build = "build.rs" diff --git a/server/Cargo.toml b/server/Cargo.toml index 5c3a9f8..085b3e8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -16,7 +16,7 @@ bench = false [package] name = "lesavka_server" -version = "0.26.1" +version = "0.26.2" edition = "2024" autobins = false diff --git a/server/src/video_sinks/webcam_sink.rs b/server/src/video_sinks/webcam_sink.rs index 8146e39..540decf 100644 --- a/server/src/video_sinks/webcam_sink.rs +++ b/server/src/video_sinks/webcam_sink.rs @@ -47,6 +47,7 @@ pub struct WebcamSink { uvc_width: u16, uvc_height: u16, direct_mjpeg_profile_mismatch_seen: AtomicBool, + unexpected_mjpeg_in_hevc_seen: AtomicBool, last_decoded_mjpeg_bytes: AtomicU64, direct_mjpeg_normalize_bypassed: AtomicBool, normalized_mjpeg_miss_count: AtomicU64, diff --git a/server/src/video_sinks/webcam_sink/constructor.rs b/server/src/video_sinks/webcam_sink/constructor.rs index 72c09a0..774241d 100644 --- a/server/src/video_sinks/webcam_sink/constructor.rs +++ b/server/src/video_sinks/webcam_sink/constructor.rs @@ -62,6 +62,7 @@ impl WebcamSink { uvc_width: cfg.width.min(u32::from(u16::MAX)) as u16, uvc_height: cfg.height.min(u32::from(u16::MAX)) as u16, direct_mjpeg_profile_mismatch_seen: AtomicBool::new(false), + unexpected_mjpeg_in_hevc_seen: AtomicBool::new(false), last_decoded_mjpeg_bytes: AtomicU64::new(0), direct_mjpeg_normalize_bypassed: AtomicBool::new(false), normalized_mjpeg_miss_count: AtomicU64::new(0), @@ -368,6 +369,7 @@ impl WebcamSink { uvc_width: cfg.width.min(u32::from(u16::MAX)) as u16, uvc_height: cfg.height.min(u32::from(u16::MAX)) as u16, direct_mjpeg_profile_mismatch_seen: AtomicBool::new(false), + unexpected_mjpeg_in_hevc_seen: AtomicBool::new(false), last_decoded_mjpeg_bytes: AtomicU64::new(0), direct_mjpeg_normalize_bypassed: AtomicBool::new(false), normalized_mjpeg_miss_count: AtomicU64::new(0), diff --git a/server/src/video_sinks/webcam_sink/frame_handoff.rs b/server/src/video_sinks/webcam_sink/frame_handoff.rs index c30c31a..c91c268 100644 --- a/server/src/video_sinks/webcam_sink/frame_handoff.rs +++ b/server/src/video_sinks/webcam_sink/frame_handoff.rs @@ -26,8 +26,24 @@ impl WebcamSink { #[cfg(not(coverage))] pub fn push(&self, pkt: VideoPacket) { + let packet_is_mjpeg = looks_like_mjpeg_frame(&pkt.data); + if packet_is_mjpeg && self.decoded_mjpeg_sink.is_some() { + if !self + .unexpected_mjpeg_in_hevc_seen + .swap(true, Ordering::Relaxed) + { + warn!( + target:"lesavka_server::video", + bytes = pkt.data.len(), + pts = pkt.pts, + "dropping MJPEG packet received while HEVC UVC decoder is active; restart or update the upstream client so it emits HEVC" + ); + } + return; + } + if let Some(path) = &self.mjpeg_spool_path - && looks_like_mjpeg_frame(&pkt.data) + && packet_is_mjpeg { self.spool_direct_mjpeg_frame(path, &pkt); return;