Reimplement in Rust CorruptionDetection RTP header extension parsing With intention to replace c++ parser and use this code to verify infrastructures are ready to support Rust in WebRTC production code Bug: webrtc:416446214 Change-Id: I7f8e13d56cf1b8bce9597047561fee07f6506025 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/497560 Reviewed-by: Victor Boivie <boivie@webrtc.org> Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#48616}
diff --git a/BUILD.gn b/BUILD.gn index 6985f3a..d466b07 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -800,6 +800,7 @@ testonly = true deps = [ "api/units:units_rust_unit_tests", + "modules/rtp_rtcp:rust_unit_tests", "rtc_base:rust_unit_tests", "rust/webrtc_import:tests", ]
diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn index 263b296..4291cbf 100644 --- a/modules/rtp_rtcp/BUILD.gn +++ b/modules/rtp_rtcp/BUILD.gn
@@ -15,6 +15,32 @@ ] } +if (rtc_rust) { + rtc_rust_library("corruption_detection_extension") { + crate_root = "corruption_detection_extension.rs" + sources = [ "corruption_detection_extension.rs" ] + } + + rtc_rust_unittest("corruption_detection_extension_test") { + crate_root = "corruption_detection_extension.rs" + sources = [ "corruption_detection_extension.rs" ] + } + + rtc_rust_cxx_bridge("corruption_detection_extension_cxx") { + allow_unsafe = true + source = "corruption_detection_extension_cxx.rs" + deps = [ + ":corruption_detection_extension", + "//build/rust:cxx_rustdeps", + ] + } + + group("rust_unit_tests") { + testonly = true + deps = [ ":corruption_detection_extension_test" ] + } +} + rtc_library("rtp_rtcp_format") { visibility = [ "*" ] public = [ @@ -1897,5 +1923,9 @@ "../../api/transport/rtp:corruption_detection_message", "../../test:test_support", ] + + if (rtc_rust) { + deps += [ ":corruption_detection_extension_cxx" ] + } } }
diff --git a/modules/rtp_rtcp/corruption_detection_extension.rs b/modules/rtp_rtcp/corruption_detection_extension.rs new file mode 100644 index 0000000..8359ead --- /dev/null +++ b/modules/rtp_rtcp/corruption_detection_extension.rs
@@ -0,0 +1,179 @@ +/* + * Copyright (c) 2026 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +const MAX_VALUE_SIZE_BYTES: usize = 16; +const MANDATORY_PAYLOAD_BYTES: usize = 1; +const CONFIGURATION_BYTES: usize = 3; +const MAX_VALUE_FOR_STD_DEV: f64 = 40.0; + +#[derive(Default)] +pub struct CorruptionDetectionExtension { + // Sequence index in the Halton sequence. + // Valid values: [0, 2^7-1] + pub sequence_index: i8, + + // Whether to interpret the `sequence_index_` as the most significant bits of + // the true sequence index. + pub interpret_sequence_index_as_most_significant_bits: bool, + + // Standard deviation of the Gaussian filter kernel. + // Valid values: [0, 40.0] + pub std_dev: f64, + + // Corruption threshold for the luma layer. + // Valid values: [0, 2^4 - 1] + pub luma_error_threshold: i8, + + // Corruption threshold for the chroma layer. + // Valid values: [0, 2^4 - 1] + pub chroma_error_threshold: i8, + + // An ordered list of samples that are the result of applying the Gaussian + // filter on the image. The coordinates of the samples and their layer are + // determined by the Halton sequence. + // An empty list should be interpreted as a way to keep the `sequence_index` + // in sync. + num_sample_values: u8, + arr_sample_values: [u8; 13], +} + +impl CorruptionDetectionExtension { + fn parse_mandatory(&mut self, data: &[u8]) { + self.interpret_sequence_index_as_most_significant_bits = data[0] >> 7 != 0; + self.sequence_index = (data[0] & 0b0111_1111) as i8; + } + + pub fn parse(&mut self, data: &[u8]) -> bool { + if data.len() == MANDATORY_PAYLOAD_BYTES { + self.parse_mandatory(data); + return true; + } + if data.len() <= CONFIGURATION_BYTES || data.len() > MAX_VALUE_SIZE_BYTES { + return false; + } + self.parse_mandatory(data); + self.std_dev = (data[1] as f64) * MAX_VALUE_FOR_STD_DEV / 255.0; + let channel_error_thresholds = data[2]; + self.luma_error_threshold = (channel_error_thresholds >> 4) as i8; + self.chroma_error_threshold = (channel_error_thresholds & 0xF) as i8; + self.num_sample_values = (data.len() - CONFIGURATION_BYTES) as u8; + self.arr_sample_values[..(self.num_sample_values as usize)] + .copy_from_slice(&data[CONFIGURATION_BYTES..]); + true + } + + pub fn sample_values(&self) -> &[u8] { + &self.arr_sample_values[..(self.num_sample_values as usize)] + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parses_mandatory_fields_from_extension() { + let raw: &[u8] = &[0b1110_1111]; + let mut message = CorruptionDetectionExtension::default(); + assert!(message.parse(raw)); + assert_eq!(message.sequence_index, 0b0110_1111); + assert!(message.interpret_sequence_index_as_most_significant_bits); + assert_eq!(message.std_dev, 0.0); + assert_eq!(message.luma_error_threshold, 0); + assert_eq!(message.chroma_error_threshold, 0); + assert!(message.sample_values().is_empty()); + } + + #[test] + fn fails_to_parse_when_given_too_few_fields() { + let raw: &[u8] = &[0b1110_1111, 8, 0]; + assert!(!CorruptionDetectionExtension::default().parse(raw)); + } + + #[test] + fn parses_everything_from_extension_with_few_samples() { + let sample_values: &[u8] = &[1, 2, 3]; + let raw: &[u8] = &[0b1100_0100, 220, 0b1110_1111, 1, 2, 3]; + let mut message = CorruptionDetectionExtension::default(); + + assert!(message.parse(raw)); + assert_eq!(message.sample_values(), sample_values); + } + + #[test] + fn parses_everything_from_extension_when_upper_bits_are_used_for_sequence_index() { + let sample_values: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; + let raw: &[u8] = &[ + 0b1100_0100, + 220, + 0b1110_1111, + sample_values[0], + sample_values[1], + sample_values[2], + sample_values[3], + sample_values[4], + sample_values[5], + sample_values[6], + sample_values[7], + sample_values[8], + sample_values[9], + sample_values[10], + sample_values[11], + sample_values[12], + ]; + let mut message = CorruptionDetectionExtension::default(); + + assert!(message.parse(raw)); + assert_eq!(message.sequence_index, 0b0100_0100); + assert!(message.interpret_sequence_index_as_most_significant_bits); + assert_eq!(message.std_dev, 34.509803921568626); // 220 / 255.0 * 40.0 + assert_eq!(message.luma_error_threshold, 0b1110); + assert_eq!(message.chroma_error_threshold, 0b1111); + assert_eq!(message.sample_values(), sample_values); + } + + #[test] + fn parses_everything_from_extension_when_lower_bits_are_used_for_sequence_index() { + let sample_values: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; + let raw: &[u8] = &[ + 0b0100_0100, + 220, + 0b1110_1111, + sample_values[0], + sample_values[1], + sample_values[2], + sample_values[3], + sample_values[4], + sample_values[5], + sample_values[6], + sample_values[7], + sample_values[8], + sample_values[9], + sample_values[10], + sample_values[11], + sample_values[12], + ]; + let mut message = CorruptionDetectionExtension::default(); + + assert!(message.parse(raw)); + assert_eq!(message.sequence_index, 0b0100_0100); + assert!(!message.interpret_sequence_index_as_most_significant_bits); + assert_eq!(message.std_dev, 34.509803921568626); // 220 / 255.0 * 40.0 + assert_eq!(message.luma_error_threshold, 0b1110); + assert_eq!(message.chroma_error_threshold, 0b1111); + assert_eq!(message.sample_values(), sample_values); + } + + #[test] + fn fails_to_parse_when_too_many_samples_are_specified() { + let raw: [u8; 17] = [42; 17]; + assert!(!CorruptionDetectionExtension::default().parse(&raw)); + } +}
diff --git a/modules/rtp_rtcp/corruption_detection_extension_cxx.rs b/modules/rtp_rtcp/corruption_detection_extension_cxx.rs new file mode 100644 index 0000000..a2485e5 --- /dev/null +++ b/modules/rtp_rtcp/corruption_detection_extension_cxx.rs
@@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +webrtc::import! { + "//modules/rtp_rtcp:corruption_detection_extension"; +} + +// CXX can't create bindings for a type from a different crate. +// This type works around such limitation by wrapping external type. +#[derive(Default)] +struct RustCorruptionDetectionExtension { + inner: corruption_detection_extension::CorruptionDetectionExtension, +} + +#[cxx::bridge(namespace = "webrtc")] +mod ffi { + extern "Rust" { + type RustCorruptionDetectionExtension; + + // CXX can't return arbitrary types by value. + // Wrapping such type into a Box works around such limitation. + fn create_corruption_detection_message() -> Box<RustCorruptionDetectionExtension>; + fn parse(&mut self, payload: &[u8]) -> bool; + + fn sequence_index(&self) -> i8; + fn interpret_sequence_index_as_most_significant_bits(&self) -> bool; + fn std_dev(&self) -> f64; + fn luma_error_threshold(&self) -> i8; + fn chroma_error_threshold(&self) -> i8; + fn sample_values(&self) -> &[u8]; + } +} + +fn create_corruption_detection_message() -> Box<RustCorruptionDetectionExtension> { + Default::default() +} + +impl RustCorruptionDetectionExtension { + fn parse(&mut self, payload: &[u8]) -> bool { + self.inner.parse(payload) + } + + fn sequence_index(&self) -> i8 { + self.inner.sequence_index + } + + fn interpret_sequence_index_as_most_significant_bits(&self) -> bool { + self.inner.interpret_sequence_index_as_most_significant_bits + } + + fn std_dev(&self) -> f64 { + self.inner.std_dev + } + + fn luma_error_threshold(&self) -> i8 { + self.inner.luma_error_threshold + } + + fn chroma_error_threshold(&self) -> i8 { + self.inner.chroma_error_threshold + } + + fn sample_values(&self) -> &[u8] { + self.inner.sample_values() + } +}
diff --git a/modules/rtp_rtcp/source/corruption_detection_extension_unittest.cc b/modules/rtp_rtcp/source/corruption_detection_extension_unittest.cc index 58f888f..f853365 100644 --- a/modules/rtp_rtcp/source/corruption_detection_extension_unittest.cc +++ b/modules/rtp_rtcp/source/corruption_detection_extension_unittest.cc
@@ -10,6 +10,7 @@ #include "modules/rtp_rtcp/source/corruption_detection_extension.h" +#include <array> #include <cstddef> #include <cstdint> #include <optional> @@ -18,6 +19,10 @@ #include "test/gmock.h" #include "test/gtest.h" +#ifndef WEBRTC_WITHOUT_RUST +#include "modules/rtp_rtcp/corruption_detection_extension_cxx.rs.h" +#endif + namespace webrtc { namespace { @@ -208,6 +213,34 @@ EXPECT_THAT(message.sample_values(), ElementsAreArray(kSampleValues)); } +#ifndef WEBRTC_WITHOUT_RUST +// Verifies interop. +// Tests to validate parser implementation are located next to it, in Rust. +TEST(CorruptionDetectionExtensionTest, RustParsesEverythingFromExtension) { + rust::Box<RustCorruptionDetectionExtension> message = + create_corruption_detection_message(); + const std::array<uint8_t, 13> kSampleValues = {1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13}; + const std::array<uint8_t, 16> kData = {0b1100'0100, 220, + 0b1110'1111, kSampleValues[0], + kSampleValues[1], kSampleValues[2], + kSampleValues[3], kSampleValues[4], + kSampleValues[5], kSampleValues[6], + kSampleValues[7], kSampleValues[8], + kSampleValues[9], kSampleValues[10], + kSampleValues[11], kSampleValues[12]}; + + EXPECT_TRUE(message->parse(rust::Slice(kData))); + EXPECT_EQ(message->sequence_index(), 0b0100'0100); + EXPECT_TRUE(message->interpret_sequence_index_as_most_significant_bits()); + EXPECT_THAT(message->std_dev(), + DoubleEq(34.509803921568626)); // 220 / 255.0 * 40.0 + EXPECT_EQ(message->luma_error_threshold(), 0b1110); + EXPECT_EQ(message->chroma_error_threshold(), 0b1111); + EXPECT_THAT(message->sample_values(), ElementsAreArray(kSampleValues)); +} +#endif + TEST(CorruptionDetectionExtensionTest, ParsesEverythingFromExtensionWhenLowerBitsAreUsedForSequenceIndex) { CorruptionDetectionMessage message;