Add message container for the corruption detection extension

Bug: b/358039777
Change-Id: I8f0fbf4b6188293efe621a509e06763bccb800b0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/359520
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Fanny Linderborg <linderborg@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42780}
diff --git a/common_video/BUILD.gn b/common_video/BUILD.gn
index 910d789..73a8adb 100644
--- a/common_video/BUILD.gn
+++ b/common_video/BUILD.gn
@@ -13,6 +13,7 @@
 
   sources = [
     "bitrate_adjuster.cc",
+    "corruption_detection_message.h",
     "frame_rate_estimator.cc",
     "frame_rate_estimator.h",
     "framerate_controller.cc",
@@ -85,6 +86,7 @@
     "../rtc_base/synchronization:mutex",
     "../rtc_base/system:rtc_export",
     "../system_wrappers:metrics",
+    "//third_party/abseil-cpp/absl/container:inlined_vector",
     "//third_party/abseil-cpp/absl/numeric:bits",
     "//third_party/abseil-cpp/absl/types:optional",
     "//third_party/libyuv",
@@ -119,6 +121,7 @@
 
     sources = [
       "bitrate_adjuster_unittest.cc",
+      "corruption_detection_message_unittest.cc",
       "frame_rate_estimator_unittest.cc",
       "framerate_controller_unittest.cc",
       "h264/h264_bitstream_parser_unittest.cc",
@@ -161,6 +164,7 @@
       "../test:test_support",
       "../test:video_test_common",
       "//testing/gtest",
+      "//third_party/abseil-cpp/absl/types:optional",
       "//third_party/libyuv",
     ]
 
diff --git a/common_video/corruption_detection_message.h b/common_video/corruption_detection_message.h
new file mode 100644
index 0000000..b6572c9
--- /dev/null
+++ b/common_video/corruption_detection_message.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#ifndef COMMON_VIDEO_CORRUPTION_DETECTION_MESSAGE_H_
+#define COMMON_VIDEO_CORRUPTION_DETECTION_MESSAGE_H_
+
+#include <cstddef>
+
+#include "absl/container/inlined_vector.h"
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+
+namespace webrtc {
+
+class CorruptionDetectionMessage {
+ public:
+  class Builder;
+
+  CorruptionDetectionMessage() = default;
+
+  CorruptionDetectionMessage(const CorruptionDetectionMessage&) = default;
+  CorruptionDetectionMessage& operator=(const CorruptionDetectionMessage&) =
+      default;
+
+  ~CorruptionDetectionMessage() = default;
+
+  int sequence_index() const { return sequence_index_; }
+  bool interpret_sequence_index_as_most_significant_bits() const {
+    return interpret_sequence_index_as_most_significant_bits_;
+  }
+  double std_dev() const { return std_dev_; }
+  int luma_error_threshold() const { return luma_error_threshold_; }
+  int chroma_error_threshold() const { return chroma_error_threshold_; }
+  rtc::ArrayView<const double> sample_values() const {
+    return rtc::MakeArrayView(sample_values_.data(), sample_values_.size());
+  }
+
+ private:
+  friend class CorruptionDetectionExtension;
+
+  static const size_t kMaxSampleSize = 13;
+
+  // Sequence index in the Halton sequence.
+  // Valid values: [0, 2^7-1]
+  int sequence_index_ = 0;
+
+  // Whether to interpret the `sequence_index_` as the most significant bits of
+  // the true sequence index.
+  bool interpret_sequence_index_as_most_significant_bits_ = false;
+
+  // Standard deviation of the Gaussian filter kernel.
+  // Valid values: [0, 40.0]
+  double std_dev_ = 0.0;
+
+  // Corruption threshold for the luma layer.
+  // Valid values: [0, 2^4 - 1]
+  int luma_error_threshold_ = 0;
+
+  // Corruption threshold for the chroma layer.
+  // Valid values: [0, 2^4 - 1]
+  int chroma_error_threshold_ = 0;
+
+  // 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.
+  absl::InlinedVector<double, kMaxSampleSize> sample_values_;
+};
+
+class CorruptionDetectionMessage::Builder {
+ public:
+  Builder() = default;
+
+  Builder(const Builder&) = default;
+  Builder& operator=(const Builder&) = default;
+
+  ~Builder() = default;
+
+  absl::optional<CorruptionDetectionMessage> Build() {
+    if (message_.sequence_index_ < 0 ||
+        message_.sequence_index_ > 0b0111'1111) {
+      return absl::nullopt;
+    }
+    if (message_.std_dev_ < 0.0 || message_.std_dev_ > 40.0) {
+      return absl::nullopt;
+    }
+    if (message_.luma_error_threshold_ < 0 ||
+        message_.luma_error_threshold_ > 15) {
+      return absl::nullopt;
+    }
+    if (message_.chroma_error_threshold_ < 0 ||
+        message_.chroma_error_threshold_ > 15) {
+      return absl::nullopt;
+    }
+    if (message_.sample_values_.size() > kMaxSampleSize) {
+      return absl::nullopt;
+    }
+    for (double sample_value : message_.sample_values_) {
+      if (sample_value < 0.0 || sample_value > 255.0) {
+        return absl::nullopt;
+      }
+    }
+    return message_;
+  }
+
+  Builder& WithSequenceIndex(int sequence_index) {
+    message_.sequence_index_ = sequence_index;
+    return *this;
+  }
+
+  Builder& WithInterpretSequenceIndexAsMostSignificantBits(
+      bool interpret_sequence_index_as_most_significant_bits) {
+    message_.interpret_sequence_index_as_most_significant_bits_ =
+        interpret_sequence_index_as_most_significant_bits;
+    return *this;
+  }
+
+  Builder& WithStdDev(double std_dev) {
+    message_.std_dev_ = std_dev;
+    return *this;
+  }
+
+  Builder& WithLumaErrorThreshold(int luma_error_threshold) {
+    message_.luma_error_threshold_ = luma_error_threshold;
+    return *this;
+  }
+
+  Builder& WithChromaErrorThreshold(int chroma_error_threshold) {
+    message_.chroma_error_threshold_ = chroma_error_threshold;
+    return *this;
+  }
+
+  Builder& WithSampleValues(const rtc::ArrayView<const double>& sample_values) {
+    message_.sample_values_.assign(sample_values.cbegin(),
+                                   sample_values.cend());
+    return *this;
+  }
+
+ private:
+  CorruptionDetectionMessage message_;
+};
+
+}  // namespace webrtc
+
+#endif  // COMMON_VIDEO_CORRUPTION_DETECTION_MESSAGE_H_
diff --git a/common_video/corruption_detection_message_unittest.cc b/common_video/corruption_detection_message_unittest.cc
new file mode 100644
index 0000000..ee11161
--- /dev/null
+++ b/common_video/corruption_detection_message_unittest.cc
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#include "common_video/corruption_detection_message.h"
+
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+
+TEST(CorruptionDetectionMessageTest, FailsToCreateWhenSequenceIndexIsTooLarge) {
+  EXPECT_EQ(CorruptionDetectionMessage::Builder()
+                .WithSequenceIndex(0b1000'0000)
+                .Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest, FailsToCreateWhenSequenceIndexIsTooSmall) {
+  EXPECT_EQ(CorruptionDetectionMessage::Builder().WithSequenceIndex(-1).Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest, FailsToCreateWhenStddevIsTooLarge) {
+  EXPECT_EQ(CorruptionDetectionMessage::Builder().WithStdDev(45.0).Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest, FailsToCreateWhenStddevIsTooSmall) {
+  EXPECT_EQ(CorruptionDetectionMessage::Builder().WithStdDev(-1.0).Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest,
+     FailsToCreateWhenLumaErrorThresholdIsTooLarge) {
+  EXPECT_EQ(
+      CorruptionDetectionMessage::Builder().WithLumaErrorThreshold(16).Build(),
+      absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest,
+     FailsToCreateWhenLumaErrorThresholdIsTooSmall) {
+  EXPECT_EQ(
+      CorruptionDetectionMessage::Builder().WithLumaErrorThreshold(-1).Build(),
+      absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest,
+     FailsToCreateWhenChromaErrorThresholdIsTooLarge) {
+  EXPECT_EQ(CorruptionDetectionMessage::Builder()
+                .WithChromaErrorThreshold(16)
+                .Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest,
+     FailsToCreateWhenChromaErrorThresholdIsTooSmall) {
+  EXPECT_EQ(CorruptionDetectionMessage::Builder()
+                .WithChromaErrorThreshold(-1)
+                .Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest,
+     FailsToCreateWhenTooManySamplesAreSpecified) {
+  const std::vector<double> kSampleValues = {1.0,  2.0,  3.0,  4.0, 5.0,
+                                             6.0,  7.0,  8.0,  9.0, 10.0,
+                                             11.0, 12.0, 13.0, 14.0};
+
+  EXPECT_EQ(CorruptionDetectionMessage::Builder()
+                .WithSampleValues(kSampleValues)
+                .Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest, FailsToCreateWhenSampleValueIsTooLarge) {
+  const std::vector<double> kSampleValues = {255.1};
+
+  EXPECT_EQ(CorruptionDetectionMessage::Builder()
+                .WithSampleValues(kSampleValues)
+                .Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest, FailsToCreateWhenSampleValueIsTooSmall) {
+  const std::vector<double> kSampleValues = {-0.1};
+
+  EXPECT_EQ(CorruptionDetectionMessage::Builder()
+                .WithSampleValues(kSampleValues)
+                .Build(),
+            absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest,
+     CreatesDefaultWhenNoParametersAreSpecified) {
+  EXPECT_NE(CorruptionDetectionMessage::Builder().Build(), absl::nullopt);
+}
+
+TEST(CorruptionDetectionMessageTest, CreatesWhenValidParametersAreSpecified) {
+  const std::vector<double> kSampleValues = {1.0, 2.0, 3.0, 4.0,  5.0,  6.0,
+                                             7.0, 8.0, 9.0, 10.0, 11.0, 12.0};
+
+  EXPECT_NE(CorruptionDetectionMessage::Builder()
+                .WithSequenceIndex(0b0111'1111)
+                .WithInterpretSequenceIndexAsMostSignificantBits(true)
+                .WithStdDev(40.0)
+                .WithLumaErrorThreshold(15)
+                .WithChromaErrorThreshold(15)
+                .WithSampleValues(kSampleValues)
+                .Build(),
+            absl::nullopt);
+}
+
+}  // namespace
+}  // namespace webrtc