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;