Generalize FuzzDataHelper::Read function

To make it easier to use in fuzzers:
- change it not to crash when there is not enough fuzz data left.
- change it to read any trivial object, not just integers

Bug: None
Change-Id: I5dfb47d3accc3c856dfc5d4376676354953d3f27
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/411660
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47035}
diff --git a/test/BUILD.gn b/test/BUILD.gn
index cf34935..96da3e9 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -782,6 +782,7 @@
         "../rtc_base/synchronization:mutex",
         "../rtc_base/system:file_wrapper",
         "../system_wrappers",
+        "fuzzers:fuzz_data_helper_unittest",
         "jitter:jitter_unittests",
         "pc/e2e:e2e_unittests",
         "pc/e2e/analyzer/video:video_analyzer_unittests",
diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn
index f44ec6a..2a78afa 100644
--- a/test/fuzzers/BUILD.gn
+++ b/test/fuzzers/BUILD.gn
@@ -33,11 +33,7 @@
 rtc_library("fuzz_data_helper") {
   testonly = true
   sources = [ "fuzz_data_helper.h" ]
-  deps = [
-    "../../modules/rtp_rtcp:rtp_rtcp_format",
-    "../../rtc_base:checks",
-    "//third_party/abseil-cpp/absl/strings:string_view",
-  ]
+  deps = [ "//third_party/abseil-cpp/absl/strings:string_view" ]
 
   # Only fuzzer targets can depend on this.
   visibility = [
@@ -46,6 +42,15 @@
   ]
 }
 
+rtc_library("fuzz_data_helper_unittest") {
+  testonly = true
+  sources = [ "fuzz_data_helper_unittest.cc" ]
+  deps = [
+    ":fuzz_data_helper",
+    "..:test_support",
+  ]
+}
+
 set_defaults("webrtc_fuzzer_test") {
   configs = rtc_add_configs
 }
diff --git a/test/fuzzers/audio_processing_sample_rate_fuzzer.cc b/test/fuzzers/audio_processing_sample_rate_fuzzer.cc
index 8ce3d05..3f7bec7 100644
--- a/test/fuzzers/audio_processing_sample_rate_fuzzer.cc
+++ b/test/fuzzers/audio_processing_sample_rate_fuzzer.cc
@@ -39,8 +39,7 @@
       AudioProcessing::GetFrameSize(input_rate);
   RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel);
   for (int i = 0; i < num_channels; ++i) {
-    float channel_value;
-    fuzz_data.CopyTo<float>(&channel_value);
+    float channel_value = fuzz_data.Read<float>();
     std::fill(float_frames[i], float_frames[i] + samples_per_input_channel,
               channel_value);
   }
diff --git a/test/fuzzers/fuzz_data_helper.h b/test/fuzzers/fuzz_data_helper.h
index 0e64869..d33f233 100644
--- a/test/fuzzers/fuzz_data_helper.h
+++ b/test/fuzzers/fuzz_data_helper.h
@@ -19,8 +19,6 @@
 #include <type_traits>
 
 #include "absl/strings/string_view.h"
-#include "modules/rtp_rtcp/source/byte_io.h"
-#include "rtc_base/checks.h"
 
 namespace webrtc {
 
@@ -35,16 +33,13 @@
 
   // Reads and returns data of type T.
   template <typename T>
-  T Read() {
-    RTC_CHECK(CanReadBytes(sizeof(T)));
-    T x = ByteReader<T>::ReadLittleEndian(&data_[data_ix_]);
-    data_ix_ += sizeof(T);
-    return x;
-  }
+    requires(std::is_trivial_v<T>)
+  T Read();
 
   // Reads and returns data of type T. Returns default_value if not enough
   // fuzzer input remains to read a T.
   template <typename T>
+    requires(std::is_trivial_v<T>)
   T ReadOrDefaultValue(T default_value) {
     if (!CanReadBytes(sizeof(T))) {
       return default_value;
@@ -54,8 +49,8 @@
 
   // Like ReadOrDefaultValue, but replaces the value 0 with default_value.
   template <typename T>
+    requires(std::is_integral_v<T>)
   T ReadOrDefaultValueNotZero(T default_value) {
-    static_assert(std::is_integral<T>::value, "");
     T x = ReadOrDefaultValue(default_value);
     return x == 0 ? default_value : x;
   }
@@ -110,11 +105,15 @@
   // If sizeof(T) > BytesLeft then the remaining bytes will be used and the rest
   // of the object will be zero initialized.
   template <typename T>
-  void CopyTo(T* object) {
-    memset(object, 0, sizeof(T));
+    requires(std::is_trivial_v<T>)
+  void CopyTo(T& object) {
+    std::span<uint8_t, sizeof(T)> object_memory(
+        reinterpret_cast<uint8_t*>(&object), sizeof(T));
 
     size_t bytes_to_copy = std::min(BytesLeft(), sizeof(T));
-    memcpy(object, data_.data() + data_ix_, bytes_to_copy);
+    std::ranges::copy(data_.subspan(data_ix_, bytes_to_copy),
+                      object_memory.begin());
+    std::ranges::fill(object_memory.subspan(bytes_to_copy), uint8_t{0});
     data_ix_ += bytes_to_copy;
   }
 
@@ -129,6 +128,28 @@
   size_t data_ix_ = 0;
 };
 
+template <typename T>
+  requires(std::is_trivial_v<T>)
+T FuzzDataHelper::Read() {
+  if constexpr (sizeof(T) == 1) {
+    if (BytesLeft() == 0) {
+      return {};
+    } else {
+      return static_cast<T>(data_[data_ix_++]);
+    }
+  }
+
+  T value;
+  CopyTo(value);
+  return value;
+}
+
+template <>
+inline bool FuzzDataHelper::Read<bool>() {
+  // Return `true' or 'false' with 50% chance each.
+  return (Read<uint8_t>() & 0b1) != 0;
+}
+
 }  // namespace webrtc
 
 #endif  // TEST_FUZZERS_FUZZ_DATA_HELPER_H_
diff --git a/test/fuzzers/fuzz_data_helper_unittest.cc b/test/fuzzers/fuzz_data_helper_unittest.cc
new file mode 100644
index 0000000..b384b6c
--- /dev/null
+++ b/test/fuzzers/fuzz_data_helper_unittest.cc
@@ -0,0 +1,75 @@
+/*
+ *  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.
+ */
+
+#include "test/fuzzers/fuzz_data_helper.h"
+
+#include <cstdint>
+
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc::test {
+namespace {
+
+using ::testing::Each;
+using ::testing::ElementsAre;
+
+TEST(FuzzDataHelperTest, ReadsIntegers) {
+  const uint8_t kData[] = {0xFE, 0x01, 0x02, 0x03, 0x04, 0x05};
+  FuzzDataHelper fuzz_data(kData);
+  ASSERT_EQ(fuzz_data.BytesLeft(), 6u);
+
+  EXPECT_EQ(fuzz_data.Read<int8_t>(), -2);
+  EXPECT_EQ(fuzz_data.BytesLeft(), 5u);
+
+  EXPECT_EQ(fuzz_data.Read<uint8_t>(), 0x01);
+  EXPECT_EQ(fuzz_data.BytesLeft(), 4u);
+
+  EXPECT_EQ(fuzz_data.Read<uint16_t>(), 0x0302);
+  EXPECT_EQ(fuzz_data.BytesLeft(), 2u);
+
+  // Read 4 bytes while only two left in `kData` - expect that succeed and
+  // returns predictable results (fills missing data with zeros).
+  EXPECT_EQ(fuzz_data.Read<uint32_t>(), 0x0000'0504u);
+  EXPECT_EQ(fuzz_data.BytesLeft(), 0u);
+}
+
+struct PlainOldData {
+  uint32_t value;
+  uint8_t array[3];
+};
+
+TEST(FuzzDataHelperTest, ReadsAnyType) {
+  const uint8_t kData[] = {0x01, 0x01, 0x02, 0x03, 0x04, 0x05};
+  FuzzDataHelper fuzz_data(kData);
+
+  PlainOldData pod = fuzz_data.Read<PlainOldData>();
+  EXPECT_EQ(pod.value, 0x03020101u);
+  EXPECT_THAT(pod.array, ElementsAre(0x04, 0x05, 0));
+}
+
+TEST(FuzzDataHelperTest, ReadsZerosWhenNoDataLeft) {
+  const uint8_t kData[] = {0xFE};
+  FuzzDataHelper fuzz_data(kData);
+  ASSERT_EQ(fuzz_data.BytesLeft(), 1u);
+  fuzz_data.Read<uint8_t>();
+
+  ASSERT_EQ(fuzz_data.BytesLeft(), 0u);
+
+  EXPECT_EQ(fuzz_data.Read<int8_t>(), 0);
+  EXPECT_EQ(fuzz_data.Read<uint8_t>(), 0u);
+  EXPECT_EQ(fuzz_data.Read<uint16_t>(), 0u);
+  PlainOldData pod = fuzz_data.Read<PlainOldData>();
+  EXPECT_EQ(pod.value, 0u);
+  EXPECT_THAT(pod.array, Each(0u));
+}
+
+}  // namespace
+}  // namespace webrtc::test
diff --git a/test/fuzzers/residual_echo_detector_fuzzer.cc b/test/fuzzers/residual_echo_detector_fuzzer.cc
index b191a64..43845bc 100644
--- a/test/fuzzers/residual_echo_detector_fuzzer.cc
+++ b/test/fuzzers/residual_echo_detector_fuzzer.cc
@@ -46,7 +46,7 @@
   echo_detector->AnalyzeCaptureAudio(input);
   for (size_t i = 0; i < 2 * kNrOfUpdates; ++i) {
     // Convert 4 input bytes to a float.
-    fuzz_data.CopyTo(&input[0]);
+    fuzz_data.CopyTo(input[0]);
     if (!isfinite(input[0]) || fabs(input[0]) > maxFuzzedValue) {
       // Ignore infinity, nan values and values that are unrealistically large.
       continue;
diff --git a/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc b/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc
index 4e1da60..bbfc288 100644
--- a/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc
+++ b/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc
@@ -38,19 +38,14 @@
   DataReader(FuzzDataHelper fuzz_data) : data_(fuzz_data) {}
 
   template <typename T>
-  void CopyTo(T* object) {
+  void CopyTo(T& object) {
     return data_.CopyTo(object);
   }
 
   template <typename T>
   T GetNum() {
-    T t = {};
-    if (data_.BytesLeft() >= sizeof(T)) {
-      CopyTo(&t);
-    }
-    return t;
+    return data_.Read<T>();
   }
-
   bool MoreToRead() { return data_.BytesLeft() > 0; }
 
  private:
@@ -63,7 +58,7 @@
   result.packetization_type = reader->GetNum<H264PacketizationTypes>();
   int nalus_length = reader->GetNum<uint8_t>();
   for (int i = 0; i < nalus_length; ++i) {
-    reader->CopyTo(&result.nalus.emplace_back());
+    reader->CopyTo(result.nalus.emplace_back());
   }
   result.packetization_mode = reader->GetNum<H264PacketizationMode>();
   return result;
@@ -123,11 +118,11 @@
     switch (codec) {
       case kVideoCodecVP8:
         reader.CopyTo(
-            &video_header.video_type_header.emplace<RTPVideoHeaderVP8>());
+            video_header.video_type_header.emplace<RTPVideoHeaderVP8>());
         break;
       case kVideoCodecVP9:
         reader.CopyTo(
-            &video_header.video_type_header.emplace<RTPVideoHeaderVP9>());
+            video_header.video_type_header.emplace<RTPVideoHeaderVP9>());
         break;
       case kVideoCodecH264:
         video_header.video_type_header = GenerateRTPVideoHeaderH264(&reader);