// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/pickle.h"

#include <stddef.h>
#include <stdint.h>

#include <deque>
#include <list>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/memory/scoped_refptr.h"
#include "base/pickle.h"
#include "net/base/pickle_traits.h"
#include "net/base/pickle_url_types.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
#include "third_party/fuzztest/src/fuzztest/fuzztest.h"
#include "url/origin.h"

namespace net {

namespace {

using ::testing::Optional;

// Tests that base::Pickle methods and WriteToPickle() interoperate. There's no
// clean way to avoid the boilerplate, so just use macros.
#define PICKLE_READ_WRITE_COMPATIBLE_TEST(PickleTypeName, value)    \
  TEST(PickleTest, PickleReadCompatible##PickleTypeName) {          \
    base::Pickle pickle;                                            \
    pickle.Write##PickleTypeName(value);                            \
    using Value = decltype(value);                                  \
    base::PickleIterator iter(pickle);                              \
    EXPECT_THAT(ReadValueFromPickle<Value>(iter), Optional(value)); \
  }                                                                 \
                                                                    \
  TEST(PickleTest, PickleWriteCompatible##PickleTypeName) {         \
    base::Pickle pickle1;                                           \
    pickle1.Write##PickleTypeName(value);                           \
    base::Pickle pickle2;                                           \
    WriteToPickle(pickle2, value);                                  \
    EXPECT_EQ(base::span(pickle1), base::span(pickle2));            \
  }                                                                 \
  static_assert(true, "eat colon")

PICKLE_READ_WRITE_COMPATIBLE_TEST(Bool, true);
PICKLE_READ_WRITE_COMPATIBLE_TEST(Int, 123);
// Don't test Long. No-one should be using Long.
PICKLE_READ_WRITE_COMPATIBLE_TEST(UInt16, uint16_t{123});
PICKLE_READ_WRITE_COMPATIBLE_TEST(UInt32, uint32_t{0xFFFFFFFF});
PICKLE_READ_WRITE_COMPATIBLE_TEST(Int64, int64_t{-42});
PICKLE_READ_WRITE_COMPATIBLE_TEST(UInt64, uint64_t{77});
// Float and Double are not supported by WriteToPickle().

PICKLE_READ_WRITE_COMPATIBLE_TEST(String, std::string("walla"));

// Generic implementation of a test that converts a value to a pickle and back
// again and checks the result matches the original.
template <typename T>
void PerformRoundTripTest(const T& value) {
  SCOPED_TRACE(PRETTY_FUNCTION);
  base::Pickle pickle;
  WriteToPickle(pickle, value);
  EXPECT_THAT(ReadValueFromPickle<T>(pickle), Optional(value));
}

// Returns a large set of test values of different types to be used in the
// following tests. These are stored in a tuple as a convenient heterogenous
// container.
auto TestData() {
  return std::make_tuple(
      true, false, 123, 0xFFFFFFFF, -42, uint8_t{0}, std::string("five"),
      std::vector<int>{1, 2, 3}, std::vector<std::string>{"foo", "bar"},
      std::optional<int>(42), std::optional<std::string>("hello"),
      std::make_pair(1, 'a'), std::make_tuple(1, 'a', 2),
      std::map<std::string, std::list<int16_t>>{
          {"foo", {1, 2, 3}}, {"bar", {}}, {"", {0x7fff}}},
      base::flat_set<uint64_t>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
      base::flat_map<std::string, std::vector<std::string>>{
          {"foo", {"bar", "baz"}}, {"", {"qux", "quux"}}},
      absl::InlinedVector<char, 5>{'a', 'c'}, std::vector<uint8_t>{1, 2, 3},
      std::deque<int16_t>{1, -1}, std::vector<bool>{true, false, true},
      absl::InlinedVector<int8_t, 256>{1, 2, 3, 4},
      std::set<int64_t>{81, 12, 17}, std::unordered_set<int64_t>{9, 3, 6},
      std::unordered_map<int, std::string>{{1, "foo"}, {2, "bar"}, {3, "baz"}},
      std::multiset<uint32_t>{1, 1, 1, 2, 2},
      std::multimap<std::string, int>{{"foo", 1}, {"foo", 2}, {"foot", 3}},
      std::unordered_multiset<int>{{1, 2, 3, 4, 5, 6, 1, 2, 3}},
      std::unordered_multimap<int, int>{{1, 2}, {1, 3}},
      std::pair<const int, int>(1, 2), std::optional<const int>(3),
      std::map<char, const std::string>{{'f', "foo"}, {'b', "bar"}},
      std::u16string(u"nicode"));
}

template <typename Functor, size_t... I>
void ForEachTestDataImpl(Functor f, std::index_sequence<I...>) {
  const auto data = TestData();
  (f(std::get<I>(data)), ...);
}

// Calls f(value) for each value in TestData().
template <typename Functor>
void ForEachTestData(Functor f) {
  ForEachTestDataImpl(
      f, std::make_index_sequence<std::tuple_size_v<decltype(TestData())>>());
}

TEST(PickleTest, RoundTrip) {
  ForEachTestData([](const auto& value) { PerformRoundTripTest(value); });
}

TEST(PickleTest, ReadValuesFromPickle) {
  base::Pickle pickle;
  WriteToPickle(pickle, 1, std::string("foo"), 'a');
  auto result = ReadValuesFromPickle<int, std::string, char>(pickle);
  EXPECT_THAT(result, Optional(std::make_tuple(1, "foo", 'a')));
}

TEST(PickleTest, RoundTripTuple) {
  base::Pickle pickle;
  WriteToPickle(pickle, TestData());
  EXPECT_THAT(ReadValueFromPickle<decltype(TestData())>(pickle),
              Optional(TestData()));
}

TEST(PickleTest, RejectEmptyPickle) {
  ForEachTestData([](const auto& value) {
    SCOPED_TRACE(PRETTY_FUNCTION);
    base::Pickle pickle;
    EXPECT_FALSE(
        ReadValueFromPickle<std::remove_cvref_t<decltype(value)>>(pickle));
  });
}

TEST(PickleTest, RejectPickleWithTooLittleData) {
  ForEachTestData([](const auto& value) {
    // Skip cases that might have enough data. base::Pickle aligns all
    // allocations to 4 bytes, so the smallest non-empty base::Pickle is 4 bytes
    // in size.
    if (sizeof(value) <= 4) {
      return;
    }
    SCOPED_TRACE(PRETTY_FUNCTION);
    base::Pickle pickle;
    WriteToPickle(pickle, 'a');
    EXPECT_FALSE(
        ReadValueFromPickle<std::remove_cvref_t<decltype(value)>>(pickle));
  });
}

TEST(PickleTest, EstimatePickleSize) {
  ForEachTestData([](const auto& value) {
    SCOPED_TRACE(PRETTY_FUNCTION);
    base::Pickle pickle;
    WriteToPickle(pickle, value);
    // EstimatePickleSize() is not guaranteed to be exact in general, but it
    // happens to be correct for the cases were are testing.
    EXPECT_EQ(pickle.payload_size(), EstimatePickleSize(value));
  });
}

TEST(PickleTest, EstimatePickleSizeMultipleValues) {
  int i = 3;
  std::string s = "word";
  bool b = true;
  EXPECT_EQ(
      EstimatePickleSize(i, s, b),
      EstimatePickleSize(i) + EstimatePickleSize(s) + EstimatePickleSize(b));
}

// Fuzz test. To cover all the cases, we need to include these types:
// * int (for kCopyAsBytes codepath)
// * std::string (for IsConstructableFromCharLikeIteratorPair path)
// * std::vector<uint16_t> (for CanResizeAndCopyFromBytes path)
// * std::vector<T> for some T that is not kCopyAsBytes, for the
//   IsReserveAndPushBackable path
// * std::set for the IsInsertAndEnable path.
// * std::tuple for the CanSerializeDeserializeTuple path,
// * bool for the bool specialization,
// * std::optional for the std::optional specialization.
using FuzzType = std::set<std::tuple<bool,
                                     std::optional<int>,
                                     std::vector<std::string>,
                                     std::vector<uint16_t>>>;

void FuzzRoundTrip(const FuzzType& value) {
  base::Pickle pickle;
  WriteToPickle(pickle, value);
  EXPECT_EQ(ReadValueFromPickle<FuzzType>(pickle), value);
}

FUZZ_TEST(PickleTest, FuzzRoundTrip);

// Tests for user-defined serialization.

class MyHostPortPair {
 public:
  MyHostPortPair(std::string_view host, uint16_t port)
      : host_(host), port_(port) {}

  const std::string& host() const { return host_; }
  uint16_t port() const { return port_; }

  bool operator==(const MyHostPortPair& other) const = default;

 private:
  std::string host_;
  uint16_t port_;
};

class MyHeaders {
 public:
  MyHeaders() = default;

  void Add(std::string_view name, std::string_view value) {
    headers_.emplace_back(name, value);
  }

  bool operator==(const MyHeaders& other) const = default;

 private:
  friend struct PickleTraits<MyHeaders>;

  std::vector<std::pair<std::string, std::string>> headers_;
};

struct MyHttpVersion {
  uint16_t major;
  uint16_t minor;

  bool operator==(const MyHttpVersion& other) const = default;
};

}  // namespace

// We cannot specialize PickleTraits from inside the anonymous namespace, so we
// had to leave it.

template <>
struct PickleTraits<MyHostPortPair> {
  static void Serialize(base::Pickle& pickle, const MyHostPortPair& value) {
    WriteToPickle(pickle, value.host(), value.port());
  }

  static std::optional<MyHostPortPair> Deserialize(base::PickleIterator& iter) {
    auto result = ReadValuesFromPickle<std::string, uint16_t>(iter);
    if (!result) {
      return std::nullopt;
    }
    auto [host, port] = std::move(result).value();
    return MyHostPortPair(host, port);
  }
};

template <>
struct PickleTraits<MyHeaders> {
  static void Serialize(base::Pickle& pickle, const MyHeaders& value) {
    WriteToPickle(pickle, value.headers_);
  }

  static std::optional<MyHeaders> Deserialize(base::PickleIterator& iter) {
    MyHeaders headers;
    if (!ReadPickleInto(iter, headers.headers_)) {
      return std::nullopt;
    }

    return headers;
  }

  static size_t PickleSize(const MyHeaders& value) {
    return EstimatePickleSize(value.headers_);
  }
};

template <>
constexpr bool kPickleAsBytes<MyHttpVersion> = true;

namespace {

TEST(PickleTraitsTest, MyHostPortPair) {
  MyHostPortPair pair("foo", 42);
  PerformRoundTripTest(pair);
}

TEST(PickleTraitsTest, MyHeaders) {
  MyHeaders headers;
  headers.Add("User-Agent", "Mozilla/5.0");
  headers.Add("Content-Type", "application/octet-stream");
  PerformRoundTripTest(headers);
}

TEST(PickleTraitsTest, MyHttpVersion) {
  MyHttpVersion version = {1, 2};
  PerformRoundTripTest(version);
}

TEST(PickleTraitsTest, UrlOrigin) {
  url::Origin origin1 = url::Origin::Create(GURL("https://example.com:443"));
  PerformRoundTripTest(origin1);

  base::Pickle pickle1;
  WriteToPickle(pickle1, origin1);
  EXPECT_EQ(pickle1.payload_size(), EstimatePickleSize(origin1));

  url::Origin origin2 = url::Origin::Create(GURL("http://example.org"));
  PerformRoundTripTest(origin2);

  base::Pickle pickle2;
  WriteToPickle(pickle2, origin2);
  EXPECT_EQ(pickle2.payload_size(), EstimatePickleSize(origin2));

  // Test opaque origin.
  url::Origin opaque_origin;
  base::Pickle pickle3;
  WriteToPickle(pickle3, opaque_origin);
  EXPECT_EQ(pickle3.payload_size(), EstimatePickleSize(opaque_origin));

  base::PickleIterator iter(pickle3);
  auto deserialized = ReadValueFromPickle<url::Origin>(iter);
  ASSERT_TRUE(deserialized.has_value());
  EXPECT_TRUE(deserialized->opaque());
}

}  // namespace
}  // namespace net
