Remove out-parameters from more codec decoding functions

Convert to using DataAndBytesConsumed.

Change-Id: Ic93ccdf9fcad7016449a0d5d1fbd4f5f1ecd8f55
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/119172
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp
index 1086371..d0bc3a9 100644
--- a/core/fpdfapi/page/cpdf_streamparser.cpp
+++ b/core/fpdfapi/page/cpdf_streamparser.cpp
@@ -103,8 +103,6 @@
                /*estimated_size=*/0)
         .bytes_consumed;
   }
-  std::unique_ptr<uint8_t, FxFreeDeleter> ignored_result;
-  uint32_t ignored_size;
   if (decoder == "DCTDecode") {
     std::unique_ptr<ScanlineDecoder> pDecoder = JpegModule::CreateDecoder(
         src_span, width, height, 0,
@@ -117,12 +115,15 @@
     return DecodeAllScanlines(std::move(pDecoder));
   }
 
-  if (decoder == "ASCII85Decode")
-    return A85Decode(src_span, &ignored_result, &ignored_size);
-  if (decoder == "ASCIIHexDecode")
-    return HexDecode(src_span, &ignored_result, &ignored_size);
-  if (decoder == "RunLengthDecode")
-    return RunLengthDecode(src_span, &ignored_result, &ignored_size);
+  if (decoder == "ASCII85Decode") {
+    return A85Decode(src_span).bytes_consumed;
+  }
+  if (decoder == "ASCIIHexDecode") {
+    return HexDecode(src_span).bytes_consumed;
+  }
+  if (decoder == "RunLengthDecode") {
+    return RunLengthDecode(src_span).bytes_consumed;
+  }
 
   return FX_INVALID_OFFSET;
 }
diff --git a/core/fpdfapi/parser/BUILD.gn b/core/fpdfapi/parser/BUILD.gn
index e3152d8..e331793 100644
--- a/core/fpdfapi/parser/BUILD.gn
+++ b/core/fpdfapi/parser/BUILD.gn
@@ -137,6 +137,7 @@
     ":parser",
     ":unit_test_support",
     "../../../constants",
+    "../../fxcodec",
     "../page",
     "../page:unit_test_support",
     "../render",
diff --git a/core/fpdfapi/parser/fpdf_parser_decode.cpp b/core/fpdfapi/parser/fpdf_parser_decode.cpp
index c6dc9b2..e9c1f8d 100644
--- a/core/fpdfapi/parser/fpdf_parser_decode.cpp
+++ b/core/fpdfapi/parser/fpdf_parser_decode.cpp
@@ -29,6 +29,7 @@
 #include "core/fxcrt/fx_extension.h"
 #include "core/fxcrt/fx_memcpy_wrappers.h"
 #include "core/fxcrt/fx_safe_types.h"
+#include "core/fxcrt/numerics/safe_conversions.h"
 #include "core/fxcrt/span.h"
 #include "core/fxcrt/span_util.h"
 #include "core/fxcrt/utf16.h"
@@ -113,13 +114,9 @@
   return true;
 }
 
-uint32_t A85Decode(pdfium::span<const uint8_t> src_span,
-                   std::unique_ptr<uint8_t, FxFreeDeleter>* dest_buf,
-                   uint32_t* dest_size) {
-  *dest_size = 0;
+DataAndBytesConsumed A85Decode(pdfium::span<const uint8_t> src_span) {
   if (src_span.empty()) {
-    dest_buf->reset();
-    return 0;
+    return {nullptr, 0u, 0u};
   }
 
   // Count legal characters and zeros.
@@ -136,8 +133,9 @@
     pos++;
   }
   // No content to decode.
-  if (pos == 0)
-    return 0;
+  if (pos == 0) {
+    return {nullptr, 0u, 0u};
+  }
 
   // Count the space needed to contain non-zero characters. The encoding ratio
   // of Ascii85 is 4:5.
@@ -145,30 +143,35 @@
   FX_SAFE_UINT32 size = zcount;
   size *= 4;
   size += space_for_non_zeroes;
-  if (!size.IsValid())
-    return FX_INVALID_OFFSET;
+  if (!size.IsValid()) {
+    return {nullptr, 0u, FX_INVALID_OFFSET};
+  }
 
-  dest_buf->reset(FX_Alloc(uint8_t, size.ValueOrDie()));
-  uint8_t* dest_buf_ptr = dest_buf->get();
+  std::unique_ptr<uint8_t, FxFreeDeleter> dest_buf(
+      FX_Alloc(uint8_t, size.ValueOrDie()));
+  uint8_t* dest_buf_ptr = dest_buf.get();
+  uint32_t dest_size = 0;
   size_t state = 0;
   uint32_t res = 0;
   pos = 0;
   while (pos < src_span.size()) {
     uint8_t ch = src_span[pos++];
-    if (PDFCharIsLineEnding(ch) || ch == ' ' || ch == '\t')
+    if (PDFCharIsLineEnding(ch) || ch == ' ' || ch == '\t') {
       continue;
+    }
 
     if (ch == 'z') {
-      UNSAFE_TODO(FXSYS_memset(dest_buf_ptr + *dest_size, 0, 4));
+      UNSAFE_TODO(FXSYS_memset(dest_buf_ptr + dest_size, 0, 4));
       state = 0;
       res = 0;
-      *dest_size += 4;
+      dest_size += 4;
       continue;
     }
 
     // Check for the end or illegal character.
-    if (ch < '!' || ch > 'u')
+    if (ch < '!' || ch > 'u') {
       break;
+    }
 
     res = res * 85 + ch - 33;
     if (state < 4) {
@@ -177,95 +180,100 @@
     }
 
     for (size_t i = 0; i < 4; ++i) {
-      UNSAFE_TODO(dest_buf_ptr[(*dest_size)++] = GetA85Result(res, i));
+      UNSAFE_TODO(dest_buf_ptr[dest_size++] = GetA85Result(res, i));
     }
     state = 0;
     res = 0;
   }
   // Handle partial group.
   if (state) {
-    for (size_t i = state; i < 5; ++i)
+    for (size_t i = state; i < 5; ++i) {
       res = res * 85 + 84;
+    }
     for (size_t i = 0; i < state - 1; ++i) {
-      UNSAFE_TODO(dest_buf_ptr[(*dest_size)++] = GetA85Result(res, i));
+      UNSAFE_TODO(dest_buf_ptr[dest_size++] = GetA85Result(res, i));
     }
   }
-  if (pos < src_span.size() && src_span[pos] == '>')
+  if (pos < src_span.size() && src_span[pos] == '>') {
     ++pos;
-  return pos;
+  }
+  return {std::move(dest_buf), dest_size, pos};
 }
 
-uint32_t HexDecode(pdfium::span<const uint8_t> src_span,
-                   std::unique_ptr<uint8_t, FxFreeDeleter>* dest_buf,
-                   uint32_t* dest_size) {
-  *dest_size = 0;
+DataAndBytesConsumed HexDecode(pdfium::span<const uint8_t> src_span) {
   if (src_span.empty()) {
-    dest_buf->reset();
-    return 0;
+    return {nullptr, 0u, 0u};
   }
 
   uint32_t i = 0;
   // Find the end of data.
-  while (i < src_span.size() && src_span[i] != '>')
+  while (i < src_span.size() && src_span[i] != '>') {
     ++i;
+  }
 
-  dest_buf->reset(FX_Alloc(uint8_t, i / 2 + 1));
-  uint8_t* dest_buf_ptr = dest_buf->get();
+  std::unique_ptr<uint8_t, FxFreeDeleter> dest_buf(
+      FX_Alloc(uint8_t, i / 2 + 1));
+  uint8_t* dest_buf_ptr = dest_buf.get();
+  uint32_t dest_size = 0;
   bool bFirst = true;
   for (i = 0; i < src_span.size(); ++i) {
     uint8_t ch = src_span[i];
-    if (PDFCharIsLineEnding(ch) || ch == ' ' || ch == '\t')
+    if (PDFCharIsLineEnding(ch) || ch == ' ' || ch == '\t') {
       continue;
+    }
 
     if (ch == '>') {
       ++i;
       break;
     }
-    if (!isxdigit(ch))
+    if (!isxdigit(ch)) {
       continue;
+    }
 
     int digit = FXSYS_HexCharToInt(ch);
     if (bFirst) {
-      UNSAFE_TODO(dest_buf_ptr[*dest_size] = digit * 16);
+      UNSAFE_TODO(dest_buf_ptr[dest_size] = digit * 16);
     } else {
-      UNSAFE_TODO(dest_buf_ptr[(*dest_size)++] += digit);
+      UNSAFE_TODO(dest_buf_ptr[dest_size++] += digit);
     }
     bFirst = !bFirst;
   }
-  if (!bFirst)
-    ++(*dest_size);
-  return i;
+  if (!bFirst) {
+    ++dest_size;
+  }
+  return {std::move(dest_buf), dest_size, i};
 }
 
-uint32_t RunLengthDecode(pdfium::span<const uint8_t> src_span,
-                         std::unique_ptr<uint8_t, FxFreeDeleter>* dest_buf,
-                         uint32_t* dest_size) {
+DataAndBytesConsumed RunLengthDecode(pdfium::span<const uint8_t> src_span) {
+  uint32_t dest_size = 0;
   size_t i = 0;
-  *dest_size = 0;
   while (i < src_span.size()) {
     if (src_span[i] == 128)
       break;
 
-    uint32_t old = *dest_size;
+    uint32_t old = dest_size;
     if (src_span[i] < 128) {
-      *dest_size += src_span[i] + 1;
-      if (*dest_size < old)
-        return FX_INVALID_OFFSET;
+      dest_size += src_span[i] + 1;
+      if (dest_size < old) {
+        return {nullptr, 0, FX_INVALID_OFFSET};
+      }
       i += src_span[i] + 2;
     } else {
-      *dest_size += 257 - src_span[i];
-      if (*dest_size < old)
-        return FX_INVALID_OFFSET;
+      dest_size += 257 - src_span[i];
+      if (dest_size < old) {
+        return {nullptr, 0, FX_INVALID_OFFSET};
+      }
       i += 2;
     }
   }
-  if (*dest_size >= kMaxStreamSize)
-    return FX_INVALID_OFFSET;
+  if (dest_size >= kMaxStreamSize) {
+    return {nullptr, 0, FX_INVALID_OFFSET};
+  }
 
-  dest_buf->reset(FX_Alloc(uint8_t, *dest_size));
+  std::unique_ptr<uint8_t, FxFreeDeleter> dest_buf(
+      FX_Alloc(uint8_t, dest_size));
   // SAFETY: allocation of `*dest_size` above.
-  auto dest_span =
-      UNSAFE_BUFFERS(pdfium::make_span(dest_buf->get(), *dest_size));
+  auto dest_span = UNSAFE_BUFFERS(pdfium::make_span(dest_buf.get(), dest_size));
   i = 0;
   int dest_count = 0;
   while (i < src_span.size()) {
@@ -292,7 +300,8 @@
       i += 2;
     }
   }
-  return std::min(i + 1, src_span.size());
+  return {std::move(dest_buf), dest_size,
+          pdfium::checked_cast<uint32_t>(std::min(i + 1, src_span.size()))};
 }
 
 std::unique_ptr<ScanlineDecoder> CreateFaxDecoder(
@@ -442,9 +451,15 @@
       new_size = decode_result.size;
       bytes_consumed = decode_result.bytes_consumed;
     } else if (decoder == "ASCII85Decode" || decoder == "A85") {
-      bytes_consumed = A85Decode(last_span, &new_buf, &new_size);
+      DataAndBytesConsumed decode_result = A85Decode(last_span);
+      new_buf = std::move(decode_result.data);
+      new_size = decode_result.size;
+      bytes_consumed = decode_result.bytes_consumed;
     } else if (decoder == "ASCIIHexDecode" || decoder == "AHx") {
-      bytes_consumed = HexDecode(last_span, &new_buf, &new_size);
+      DataAndBytesConsumed decode_result = HexDecode(last_span);
+      new_buf = std::move(decode_result.data);
+      new_size = decode_result.size;
+      bytes_consumed = decode_result.bytes_consumed;
     } else if (decoder == "RunLengthDecode" || decoder == "RL") {
       if (bImageAcc && i == nSize - 1) {
         *ImageEncoding = "RunLengthDecode";
@@ -453,7 +468,10 @@
         *pImageParams = std::move(pParam);
         return true;
       }
-      bytes_consumed = RunLengthDecode(last_span, &new_buf, &new_size);
+      DataAndBytesConsumed decode_result = RunLengthDecode(last_span);
+      new_buf = std::move(decode_result.data);
+      new_size = decode_result.size;
+      bytes_consumed = decode_result.bytes_consumed;
     } else {
       // If we get here, assume it's an image decoder.
       if (decoder == "DCT")
diff --git a/core/fpdfapi/parser/fpdf_parser_decode.h b/core/fpdfapi/parser/fpdf_parser_decode.h
index d53d0b2..2fd7cb0 100644
--- a/core/fpdfapi/parser/fpdf_parser_decode.h
+++ b/core/fpdfapi/parser/fpdf_parser_decode.h
@@ -54,17 +54,12 @@
     int bpc,
     const CPDF_Dictionary* pParams);
 
-uint32_t RunLengthDecode(pdfium::span<const uint8_t> src_span,
-                         std::unique_ptr<uint8_t, FxFreeDeleter>* dest_buf,
-                         uint32_t* dest_size);
+fxcodec::DataAndBytesConsumed RunLengthDecode(
+    pdfium::span<const uint8_t> src_span);
 
-uint32_t A85Decode(pdfium::span<const uint8_t> src_span,
-                   std::unique_ptr<uint8_t, FxFreeDeleter>* dest_buf,
-                   uint32_t* dest_size);
+fxcodec::DataAndBytesConsumed A85Decode(pdfium::span<const uint8_t> src_span);
 
-uint32_t HexDecode(pdfium::span<const uint8_t> src_span,
-                   std::unique_ptr<uint8_t, FxFreeDeleter>* dest_buf,
-                   uint32_t* dest_size);
+fxcodec::DataAndBytesConsumed HexDecode(pdfium::span<const uint8_t> src_span);
 
 fxcodec::DataAndBytesConsumed FlateOrLZWDecode(
     bool use_lzw,
diff --git a/core/fpdfapi/parser/fpdf_parser_decode_unittest.cpp b/core/fpdfapi/parser/fpdf_parser_decode_unittest.cpp
index 61d74b4..806f279 100644
--- a/core/fpdfapi/parser/fpdf_parser_decode_unittest.cpp
+++ b/core/fpdfapi/parser/fpdf_parser_decode_unittest.cpp
@@ -20,6 +20,7 @@
 #include "core/fpdfapi/parser/cpdf_name.h"
 #include "core/fpdfapi/parser/cpdf_reference.h"
 #include "core/fpdfapi/parser/cpdf_string.h"
+#include "core/fxcodec/data_and_bytes_consumed.h"
 #include "core/fxcrt/bytestring.h"
 #include "core/fxcrt/fx_memory_wrappers.h"
 #include "core/fxcrt/span.h"
@@ -287,16 +288,13 @@
       STR_IN_OUT_CASE("FCfN8FCfN8vw", "testtest", 11),
   };
   for (const auto& test_case : kTestData) {
-    std::unique_ptr<uint8_t, FxFreeDeleter> result;
-    uint32_t result_size = 0;
-    EXPECT_EQ(test_case.processed_size,
-              A85Decode(UNSAFE_TODO(pdfium::make_span(test_case.input,
-                                                      test_case.input_size)),
-                        &result, &result_size))
+    DataAndBytesConsumed result = A85Decode(
+        UNSAFE_TODO(pdfium::make_span(test_case.input, test_case.input_size)));
+    EXPECT_EQ(test_case.processed_size, result.bytes_consumed)
         << "for case " << test_case.input;
-    ASSERT_EQ(test_case.expected_size, result_size);
-    const uint8_t* result_ptr = result.get();
-    for (size_t j = 0; j < result_size; ++j) {
+    ASSERT_EQ(test_case.expected_size, result.size);
+    const uint8_t* result_ptr = result.data.get();
+    for (size_t j = 0; j < result.size; ++j) {
       EXPECT_EQ(test_case.expected[j], result_ptr[j])
           << "for case " << test_case.input << " char " << j;
     }
@@ -323,16 +321,13 @@
       STR_IN_OUT_CASE("12AcED3c3456", "\x12\xac\xed\x3c\x34\x56", 12),
   };
   for (const auto& test_case : kTestData) {
-    std::unique_ptr<uint8_t, FxFreeDeleter> result;
-    uint32_t result_size = 0;
-    EXPECT_EQ(test_case.processed_size,
-              HexDecode(UNSAFE_TODO(pdfium::make_span(test_case.input,
-                                                      test_case.input_size)),
-                        &result, &result_size))
+    DataAndBytesConsumed result = HexDecode(
+        UNSAFE_TODO(pdfium::make_span(test_case.input, test_case.input_size)));
+    EXPECT_EQ(test_case.processed_size, result.bytes_consumed)
         << "for case " << test_case.input;
-    ASSERT_EQ(test_case.expected_size, result_size);
-    const uint8_t* result_ptr = result.get();
-    for (size_t j = 0; j < result_size; ++j) {
+    ASSERT_EQ(test_case.expected_size, result.size);
+    const uint8_t* result_ptr = result.data.get();
+    for (size_t j = 0; j < result.size; ++j) {
       EXPECT_EQ(test_case.expected[j], result_ptr[j])
           << "for case " << test_case.input << " char " << j;
     }
diff --git a/core/fxcodec/basic/rle_unittest.cpp b/core/fxcodec/basic/rle_unittest.cpp
index 87a5213..4f150b0 100644
--- a/core/fxcodec/basic/rle_unittest.cpp
+++ b/core/fxcodec/basic/rle_unittest.cpp
@@ -14,6 +14,7 @@
 
 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
 #include "core/fxcodec/basic/basicmodule.h"
+#include "core/fxcodec/data_and_bytes_consumed.h"
 #include "core/fxcrt/data_vector.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -34,62 +35,56 @@
 // Check a few basic cases (2 matching runs in a row, matching run followed
 // by a non-matching run, and non-matching run followed by a matching run).
 TEST(fxcodec, RLENormalInputs) {
-  std::unique_ptr<uint8_t, FxFreeDeleter> decoded_buf;
-  uint32_t decoded_size = 0;
-
   {
     // Case 1: Match, match
     const uint8_t src_buf_1[] = {2, 2, 2, 2, 4, 4, 4, 4, 4, 4};
     DataVector<uint8_t> dest_buf = BasicModule::RunLengthEncode(src_buf_1);
-    RunLengthDecode(dest_buf, &decoded_buf, &decoded_size);
-    ASSERT_EQ(sizeof(src_buf_1), decoded_size);
-    auto decoded_buf_span = pdfium::make_span(decoded_buf.get(), decoded_size);
-    for (uint32_t i = 0; i < decoded_size; i++)
+    DataAndBytesConsumed result = RunLengthDecode(dest_buf);
+    ASSERT_EQ(sizeof(src_buf_1), result.size);
+    auto decoded_buf_span = pdfium::make_span(result.data.get(), result.size);
+    for (uint32_t i = 0; i < result.size; i++) {
       EXPECT_EQ(src_buf_1[i], decoded_buf_span[i]) << " at " << i;
+    }
   }
 
   {
     // Case 2: Match, non-match
     const uint8_t src_buf_2[] = {2, 2, 2, 2, 1, 2, 3, 4, 5, 6};
     DataVector<uint8_t> dest_buf = BasicModule::RunLengthEncode(src_buf_2);
-    decoded_buf.reset();
-    decoded_size = 0;
-    RunLengthDecode(dest_buf, &decoded_buf, &decoded_size);
-    ASSERT_EQ(sizeof(src_buf_2), decoded_size);
-    auto decoded_buf_span = pdfium::make_span(decoded_buf.get(), decoded_size);
-    for (uint32_t i = 0; i < decoded_size; i++)
+    DataAndBytesConsumed result = RunLengthDecode(dest_buf);
+    ASSERT_EQ(sizeof(src_buf_2), result.size);
+    auto decoded_buf_span = pdfium::make_span(result.data.get(), result.size);
+    for (uint32_t i = 0; i < result.size; i++) {
       EXPECT_EQ(src_buf_2[i], decoded_buf_span[i]) << " at " << i;
+    }
   }
 
   {
     // Case 3: Non-match, match
     const uint8_t src_buf_3[] = {1, 2, 3, 4, 5, 3, 3, 3, 3, 3};
     DataVector<uint8_t> dest_buf = BasicModule::RunLengthEncode(src_buf_3);
-    decoded_buf.reset();
-    decoded_size = 0;
-    RunLengthDecode(dest_buf, &decoded_buf, &decoded_size);
-    ASSERT_EQ(sizeof(src_buf_3), decoded_size);
-    auto decoded_buf_span = pdfium::make_span(decoded_buf.get(), decoded_size);
-    for (uint32_t i = 0; i < decoded_size; i++)
+    DataAndBytesConsumed result = RunLengthDecode(dest_buf);
+    ASSERT_EQ(sizeof(src_buf_3), result.size);
+    auto decoded_buf_span = pdfium::make_span(result.data.get(), result.size);
+    for (uint32_t i = 0; i < result.size; i++) {
       EXPECT_EQ(src_buf_3[i], decoded_buf_span[i]) << " at " << i;
+    }
   }
 }
 
 // Check that runs longer than 128 are broken up properly, both matched and
 // non-matched.
 TEST(fxcodec, RLEFullLengthInputs) {
-  std::unique_ptr<uint8_t, FxFreeDeleter> decoded_buf;
-  uint32_t decoded_size = 0;
-
   {
     // Case 1: Match, match
     const uint8_t src_buf_1[260] = {1};
     DataVector<uint8_t> dest_buf = BasicModule::RunLengthEncode(src_buf_1);
-    RunLengthDecode(dest_buf, &decoded_buf, &decoded_size);
-    ASSERT_EQ(sizeof(src_buf_1), decoded_size);
-    auto decoded_buf_span = pdfium::make_span(decoded_buf.get(), decoded_size);
-    for (uint32_t i = 0; i < decoded_size; i++)
+    DataAndBytesConsumed result = RunLengthDecode(dest_buf);
+    ASSERT_EQ(sizeof(src_buf_1), result.size);
+    auto decoded_buf_span = pdfium::make_span(result.data.get(), result.size);
+    for (uint32_t i = 0; i < result.size; i++) {
       EXPECT_EQ(src_buf_1[i], decoded_buf_span[i]) << " at " << i;
+    }
   }
 
   {
@@ -98,13 +93,12 @@
     for (uint16_t i = 128; i < 260; i++)
       src_buf_2[i] = static_cast<uint8_t>(i - 125);
     DataVector<uint8_t> dest_buf = BasicModule::RunLengthEncode(src_buf_2);
-    decoded_buf.reset();
-    decoded_size = 0;
-    RunLengthDecode(dest_buf, &decoded_buf, &decoded_size);
-    ASSERT_EQ(sizeof(src_buf_2), decoded_size);
-    auto decoded_buf_span = pdfium::make_span(decoded_buf.get(), decoded_size);
-    for (uint32_t i = 0; i < decoded_size; i++)
+    DataAndBytesConsumed result = RunLengthDecode(dest_buf);
+    ASSERT_EQ(sizeof(src_buf_2), result.size);
+    auto decoded_buf_span = pdfium::make_span(result.data.get(), result.size);
+    for (uint32_t i = 0; i < result.size; i++) {
       EXPECT_EQ(src_buf_2[i], decoded_buf_span[i]) << " at " << i;
+    }
   }
 
   {
@@ -113,13 +107,12 @@
     for (uint8_t i = 0; i < 128; i++)
       src_buf_3[i] = i;
     DataVector<uint8_t> dest_buf = BasicModule::RunLengthEncode(src_buf_3);
-    decoded_buf.reset();
-    decoded_size = 0;
-    RunLengthDecode(dest_buf, &decoded_buf, &decoded_size);
-    ASSERT_EQ(sizeof(src_buf_3), decoded_size);
-    auto decoded_buf_span = pdfium::make_span(decoded_buf.get(), decoded_size);
-    for (uint32_t i = 0; i < decoded_size; i++)
+    DataAndBytesConsumed result = RunLengthDecode(dest_buf);
+    ASSERT_EQ(sizeof(src_buf_3), result.size);
+    auto decoded_buf_span = pdfium::make_span(result.data.get(), result.size);
+    for (uint32_t i = 0; i < result.size; i++) {
       EXPECT_EQ(src_buf_3[i], decoded_buf_span[i]) << " at " << i;
+    }
   }
 
   {
@@ -128,12 +121,11 @@
     for (uint16_t i = 0; i < 260; i++)
       src_buf_4[i] = static_cast<uint8_t>(i);
     DataVector<uint8_t> dest_buf = BasicModule::RunLengthEncode(src_buf_4);
-    decoded_buf.reset();
-    decoded_size = 0;
-    RunLengthDecode(dest_buf, &decoded_buf, &decoded_size);
-    ASSERT_EQ(sizeof(src_buf_4), decoded_size);
-    auto decoded_buf_span = pdfium::make_span(decoded_buf.get(), decoded_size);
-    for (uint32_t i = 0; i < decoded_size; i++)
+    DataAndBytesConsumed result = RunLengthDecode(dest_buf);
+    ASSERT_EQ(sizeof(src_buf_4), result.size);
+    auto decoded_buf_span = pdfium::make_span(result.data.get(), result.size);
+    for (uint32_t i = 0; i < result.size; i++) {
       EXPECT_EQ(src_buf_4[i], decoded_buf_span[i]) << " at " << i;
+    }
   }
 }
diff --git a/fpdfsdk/BUILD.gn b/fpdfsdk/BUILD.gn
index c8aa54f..b18ca03 100644
--- a/fpdfsdk/BUILD.gn
+++ b/fpdfsdk/BUILD.gn
@@ -76,6 +76,7 @@
     "../core/fpdfapi/render",
     "../core/fpdfdoc",
     "../core/fpdftext",
+    "../core/fxcodec",
     "../core/fxcrt",
     "../core/fxge",
     "../fxjs",
diff --git a/fpdfsdk/fpdf_attachment.cpp b/fpdfsdk/fpdf_attachment.cpp
index 85ba9fb..4136ad5 100644
--- a/fpdfsdk/fpdf_attachment.cpp
+++ b/fpdfsdk/fpdf_attachment.cpp
@@ -22,6 +22,7 @@
 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
 #include "core/fpdfdoc/cpdf_filespec.h"
 #include "core/fpdfdoc/cpdf_nametree.h"
+#include "core/fxcodec/data_and_bytes_consumed.h"
 #include "core/fxcrt/cfx_datetime.h"
 #include "core/fxcrt/data_vector.h"
 #include "core/fxcrt/fx_extension.h"
@@ -34,10 +35,8 @@
 constexpr char kChecksumKey[] = "CheckSum";
 
 ByteString CFXByteStringHexDecode(const ByteString& bsHex) {
-  std::unique_ptr<uint8_t, FxFreeDeleter> result;
-  uint32_t size = 0;
-  HexDecode(bsHex.unsigned_span(), &result, &size);
-  return ByteString(result.get(), size);
+  DataAndBytesConsumed result = HexDecode(bsHex.unsigned_span());
+  return ByteString(result.data.get(), result.size);
 }
 
 // TODO(tsepez): should be UNSAFE_BUFFER_USAGE.