Protected Audiences: store ads in protobuf

Loading ads for interest groups is slow -- store ads as protobof instead of JSON to decrease parsing time.

Change-Id: I12bb147db6afa986153ea052be798ed797c5f696
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4822944
Reviewed-by: Russ Hamilton <behamilton@google.com>
Commit-Queue: Abigail Katcoff <abigailkatcoff@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1190815}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 51abfe24..1327128 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -135,6 +135,7 @@
     "//content/browser/download:proto",
     "//content/browser/file_system_access:proto",
     "//content/browser/indexed_db:mojo_bindings",
+    "//content/browser/interest_group:interest_group_proto",
     "//content/browser/notifications:notification_proto",
     "//content/browser/payments:payment_app_proto",
     "//content/browser/private_aggregation/proto:private_aggregation_budgets_proto",
diff --git a/content/browser/interest_group/BUILD.gn b/content/browser/interest_group/BUILD.gn
new file mode 100644
index 0000000..75de454
--- /dev/null
+++ b/content/browser/interest_group/BUILD.gn
@@ -0,0 +1,9 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("interest_group_proto") {
+  sources = [ "interest_group_ad.proto" ]
+}
diff --git a/content/browser/interest_group/interest_group_ad.proto b/content/browser/interest_group/interest_group_ad.proto
new file mode 100644
index 0000000..92f8893a
--- /dev/null
+++ b/content/browser/interest_group/interest_group_ad.proto
@@ -0,0 +1,22 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto3";
+
+option optimize_for = LITE_RUNTIME;
+
+package content;
+
+message AdProtos {
+  message AdProto {
+    string render_url = 1;
+    optional string size_group = 2;
+    optional string metadata = 3;
+    optional string buyer_reporting_id = 4;
+    optional string buyer_and_seller_reporting_id = 5;
+    optional string ad_render_id = 6;
+    repeated string allowed_reporting_origins = 7;
+  }
+  repeated AdProto ads = 1;
+}
diff --git a/content/browser/interest_group/interest_group_storage.cc b/content/browser/interest_group/interest_group_storage.cc
index e569ea4..52dc2060 100644
--- a/content/browser/interest_group/interest_group_storage.cc
+++ b/content/browser/interest_group/interest_group_storage.cc
@@ -28,6 +28,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
+#include "content/browser/interest_group/interest_group_ad.pb.h"
 #include "content/browser/interest_group/interest_group_k_anonymity_manager.h"
 #include "content/browser/interest_group/interest_group_update.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
@@ -71,6 +72,8 @@
 // Version 10 - 2022/08 - crrev.com/c/3818142
 // Version 13 - 2023/01 - crrev.com/c/4167800
 // Version 14 - 2023/08 - crrev.com/c/4739632
+// Version 15 - 2023/08 - crrev.com/c/4808727
+// Version 16 - 2023/08 - crrev.com/c/4822944
 //
 // Version 1 adds a table for interest groups.
 // Version 2 adds a column for rate limiting interest group updates.
@@ -87,11 +90,13 @@
 // Version 13 adds ad size-related fields (ad_sizes & size_groups).
 // Version 14 adds auction server request flags.
 // Version 15 adds an additional bid key field.
-const int kCurrentVersionNumber = 15;
+// Version 16 changes the ads and ad component columns of the interest group
+// table to protobuf format.
+const int kCurrentVersionNumber = 16;
 
 // Earliest version of the code which can use a |kCurrentVersionNumber|
 // database without failing.
-const int kCompatibleVersionNumber = 15;
+const int kCompatibleVersionNumber = 16;
 
 // Latest version of the database that cannot be upgraded to
 // |kCurrentVersionNumber| without razing the database.
@@ -134,36 +139,6 @@
   return result;
 }
 
-base::Value ToValue(const blink::InterestGroup::Ad& ad) {
-  base::Value value(base::Value::Type::DICT);
-  base::Value::Dict& dict = value.GetDict();
-  dict.Set("url", ad.render_url.spec());
-  if (ad.size_group) {
-    dict.Set("size_group", ad.size_group.value());
-  }
-  if (ad.buyer_reporting_id) {
-    dict.Set("buyer_reporting_id", ad.buyer_reporting_id.value());
-  }
-  if (ad.buyer_and_seller_reporting_id) {
-    dict.Set("buyer_and_seller_reporting_id",
-             ad.buyer_and_seller_reporting_id.value());
-  }
-  if (ad.metadata) {
-    dict.Set("metadata", ad.metadata.value());
-  }
-  if (ad.ad_render_id) {
-    dict.Set("ad_render_id", ad.ad_render_id.value());
-  }
-  if (ad.allowed_reporting_origins) {
-    base::Value::List allowed_reporting_origins;
-    for (const auto& origin : ad.allowed_reporting_origins.value()) {
-      allowed_reporting_origins.Append(Serialize(origin));
-    }
-    dict.Set("allowed_reporting_origins", std::move(allowed_reporting_origins));
-  }
-  return value;
-}
-
 blink::InterestGroup::Ad FromInterestGroupAdValue(const base::Value::Dict& dict,
                                                   bool for_components) {
   blink::InterestGroup::Ad result;
@@ -244,20 +219,50 @@
   return base::flat_map<std::string, double>(std::move(pairs));
 }
 
+AdProtos GetAdProtosFromAds(std::vector<blink::InterestGroup::Ad> ads) {
+  AdProtos ad_protos;
+  for (blink::InterestGroup::Ad ad : ads) {
+    AdProtos_AdProto* ad_proto = ad_protos.add_ads();
+    ad_proto->set_render_url(ad.render_url.spec());
+    if (ad.size_group.has_value()) {
+      ad_proto->set_size_group(*ad.size_group);
+    }
+    if (ad.metadata.has_value()) {
+      ad_proto->set_metadata(*ad.metadata);
+    }
+    if (ad.buyer_reporting_id.has_value()) {
+      ad_proto->set_buyer_reporting_id(*ad.buyer_reporting_id);
+    }
+    if (ad.buyer_and_seller_reporting_id.has_value()) {
+      ad_proto->set_buyer_and_seller_reporting_id(
+          *ad.buyer_and_seller_reporting_id);
+    }
+    if (ad.ad_render_id.has_value()) {
+      ad_proto->set_ad_render_id(*ad.ad_render_id);
+    }
+    if (ad.allowed_reporting_origins.has_value()) {
+      for (auto allowed_reporting_origin : *ad.allowed_reporting_origins) {
+        ad_proto->add_allowed_reporting_origins(
+            allowed_reporting_origin.Serialize());
+      }
+    }
+  }
+  return ad_protos;
+}
+
 std::string Serialize(
     const absl::optional<std::vector<blink::InterestGroup::Ad>>& ads) {
-  if (!ads) {
-    return std::string();
-  }
-  base::Value::List list;
-  for (const auto& ad : ads.value()) {
-    list.Append(ToValue(ad));
-  }
-  return Serialize(list);
+  std::string serialized_ads;
+  AdProtos ad_protos =
+      ads.has_value() ? GetAdProtosFromAds(ads.value()) : AdProtos();
+
+  ad_protos.SerializeToString(&serialized_ads);
+  return serialized_ads;
 }
+
 absl::optional<std::vector<blink::InterestGroup::Ad>>
-DeserializeInterestGroupAdVector(const std::string& serialized_ads,
-                                 bool for_components) {
+DeserializeInterestGroupAdVectorJson(const std::string& serialized_ads,
+                                     bool for_components) {
   std::unique_ptr<base::Value> ads_value = DeserializeValue(serialized_ads);
   if (!ads_value || !ads_value->is_list()) {
     return absl::nullopt;
@@ -272,6 +277,50 @@
   return result;
 }
 
+absl::optional<std::vector<blink::InterestGroup::Ad>>
+DeserializeInterestGroupAdVectorProto(const std::string& serialized_ads) {
+  AdProtos ad_protos;
+
+  bool success = ad_protos.ParseFromString(serialized_ads);
+
+  if (not success || ad_protos.ads().empty()) {
+    return absl::nullopt;
+  }
+  std::vector<blink::InterestGroup::Ad> out;
+  for (const auto& ad_proto : ad_protos.ads()) {
+    blink::InterestGroup::Ad ad;
+    ad.render_url = GURL(ad_proto.render_url());
+    if (ad_proto.has_size_group()) {
+      ad.size_group = ad_proto.size_group();
+    }
+    if (ad_proto.has_metadata()) {
+      ad.metadata = ad_proto.metadata();
+    }
+    if (ad_proto.has_buyer_reporting_id()) {
+      ad.buyer_reporting_id = ad_proto.buyer_reporting_id();
+    }
+    if (ad_proto.has_buyer_and_seller_reporting_id()) {
+      ad.buyer_and_seller_reporting_id =
+          ad_proto.buyer_and_seller_reporting_id();
+    }
+    if (ad_proto.has_ad_render_id()) {
+      ad.ad_render_id = ad_proto.ad_render_id();
+    }
+    if (!ad_proto.allowed_reporting_origins().empty()) {
+      std::vector<url::Origin> allowed_reporting_origins_vector;
+      for (std::string allowed_reporting_origin :
+           ad_proto.allowed_reporting_origins()) {
+        allowed_reporting_origins_vector.emplace_back(
+            DeserializeOrigin(allowed_reporting_origin));
+      }
+      ad.allowed_reporting_origins =
+          std::move(allowed_reporting_origins_vector);
+    }
+    out.push_back(ad);
+  }
+  return out;
+}
+
 std::string Serialize(
     const absl::optional<base::flat_map<std::string, blink::AdSize>>&
         ad_sizes) {
@@ -547,7 +596,7 @@
 
 // Initializes the tables, returning true on success.
 // The tables cannot exist when calling this function.
-bool CreateV15Schema(sql::Database& db) {
+bool CreateV16Schema(sql::Database& db) {
   // IMPORTANT: If you add or remove fields, you need to update
   // `ClearExcessiveStorage()` to consider the size of added/removed fields for
   // storage usage calculations.
@@ -580,8 +629,8 @@
         "trusted_bidding_signals_url TEXT NOT NULL,"
         "trusted_bidding_signals_keys TEXT NOT NULL,"
         "user_bidding_signals TEXT,"
-        "ads TEXT NOT NULL,"
-        "ad_components TEXT NOT NULL,"
+        "ads_pb BLOB NOT NULL,"
+        "ad_components_pb BLOB NOT NULL,"
         "ad_sizes TEXT NOT NULL,"
         "size_groups TEXT NOT NULL,"
         "auction_server_request_flags INTEGER NOT NULL,"
@@ -681,6 +730,134 @@
   return true;
 }
 
+bool UpgradeV15SchemaToV16(sql::Database& db, sql::MetaTable& meta_table) {
+  static const char kInterestGroupTableSql[] =
+      // clang-format off
+      "CREATE TABLE new_interest_groups("
+        "expiration INTEGER NOT NULL,"
+        "last_updated INTEGER NOT NULL,"
+        "next_update_after INTEGER NOT NULL,"
+        "owner TEXT NOT NULL,"
+        "joining_origin TEXT NOT NULL,"
+        "exact_join_time INTEGER NOT NULL,"
+        "name TEXT NOT NULL,"
+        "priority DOUBLE NOT NULL,"
+        "enable_bidding_signals_prioritization INTEGER NOT NULL,"
+        "priority_vector TEXT NOT NULL,"
+        "priority_signals_overrides TEXT NOT NULL,"
+        "seller_capabilities TEXT NOT NULL,"
+        "all_sellers_capabilities INTEGER NOT NULL,"
+        "execution_mode INTEGER NOT NULL,"
+        "joining_url TEXT NOT NULL,"
+        "bidding_url TEXT NOT NULL,"
+        "bidding_wasm_helper_url TEXT NOT NULL,"
+        "update_url TEXT NOT NULL,"
+        "trusted_bidding_signals_url TEXT NOT NULL,"
+        "trusted_bidding_signals_keys TEXT NOT NULL,"
+        "user_bidding_signals TEXT,"
+        "ads_pb BLOB NOT NULL,"
+        "ad_components_pb BLOB NOT NULL,"
+        "ad_sizes TEXT NOT NULL,"
+        "size_groups TEXT NOT NULL,"
+        "auction_server_request_flags INTEGER NOT NULL,"
+        "additional_bid_key BLOB NOT NULL,"
+      "PRIMARY KEY(owner,name))";
+  // clang-format on
+  if (!db.Execute(kInterestGroupTableSql)) {
+    return false;
+  }
+
+  // clang-format off
+  sql::Statement kCopyInterestGroupTableWithEmptyAdPBsSql(
+      db.GetCachedStatement(SQL_FROM_HERE,
+      "INSERT INTO new_interest_groups "
+      "SELECT expiration,"
+             "last_updated,"
+             "next_update_after,"
+             "owner,"
+             "joining_origin,"
+             "exact_join_time,"
+             "name,"
+             "priority,"
+             "enable_bidding_signals_prioritization,"
+             "priority_vector,"
+             "priority_signals_overrides,"
+             "seller_capabilities,"
+             "all_sellers_capabilities,"
+             "execution_mode,"
+             "joining_url,"
+             "bidding_url,"
+             "bidding_wasm_helper_url,"
+             "update_url,"
+             "trusted_bidding_signals_url,"
+             "trusted_bidding_signals_keys,"
+             "user_bidding_signals,"
+             "?," // ads_pb
+             "?," // ad_components_pb
+             "ad_sizes,"
+             "size_groups,"
+             "auction_server_request_flags,"
+             "additional_bid_key "
+      "FROM interest_groups"));
+  // clang-format on
+  std::string empty_ad_proto_value;
+  AdProtos().SerializeToString(&empty_ad_proto_value);
+  kCopyInterestGroupTableWithEmptyAdPBsSql.BindBlob(0, empty_ad_proto_value);
+  kCopyInterestGroupTableWithEmptyAdPBsSql.BindBlob(1, empty_ad_proto_value);
+
+  if (!kCopyInterestGroupTableWithEmptyAdPBsSql.Run()) {
+    return false;
+  }
+
+  sql::Statement kSelectIGsWithAds(db.GetCachedStatement(
+      SQL_FROM_HERE,
+      "SELECT owner, name, ads, ad_components from interest_groups"));
+
+  while (kSelectIGsWithAds.Step()) {
+    std::string owner = kSelectIGsWithAds.ColumnString(0);
+    std::string name = kSelectIGsWithAds.ColumnString(1);
+    absl::optional<std::vector<blink::InterestGroup::Ad>> ads =
+        DeserializeInterestGroupAdVectorJson(kSelectIGsWithAds.ColumnString(2),
+                                             /*for_components=*/false);
+    absl::optional<std::vector<blink::InterestGroup::Ad>> ad_components =
+        DeserializeInterestGroupAdVectorJson(kSelectIGsWithAds.ColumnString(3),
+                                             /*for_components=*/true);
+
+    std::string serialized_ads = Serialize(ads);
+    std::string serialized_ad_components = Serialize(ad_components);
+
+    sql::Statement insert_value_into_IG(db.GetCachedStatement(
+        SQL_FROM_HERE,
+        "UPDATE new_interest_groups SET ads_pb = ?, ad_components_pb = ? "
+        "WHERE owner = ? AND name = ?"));
+
+    insert_value_into_IG.BindBlob(0, serialized_ads);
+    insert_value_into_IG.BindBlob(1, serialized_ad_components);
+    insert_value_into_IG.BindString(2, owner);
+    insert_value_into_IG.BindString(3, name);
+
+    if (!insert_value_into_IG.Run()) {
+      return false;
+    }
+  }
+
+  static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
+  if (!db.Execute(kDropInterestGroupTableSql)) {
+    return false;
+  }
+
+  static const char kRenameInterestGroupTableSql[] =
+      // clang-format off
+      "ALTER TABLE new_interest_groups "
+      "RENAME TO interest_groups";
+  // clang-format on
+  if (!db.Execute(kRenameInterestGroupTableSql)) {
+    return false;
+  }
+
+  return CreateInterestGroupIndices(db);
+}
+
 bool UpgradeV14SchemaToV15(sql::Database& db, sql::MetaTable& meta_table) {
   static const char kInterestGroupTableSql[] =
       // clang-format off
@@ -1453,8 +1630,8 @@
           "trusted_bidding_signals_url,"
           "trusted_bidding_signals_keys,"
           "user_bidding_signals,"  // opaque data
-          "ads,"
-          "ad_components,"
+          "ads_pb,"
+          "ad_components_pb,"
           "ad_sizes,"
           "size_groups,"
           "auction_server_request_flags,"
@@ -1507,10 +1684,9 @@
   if (load.GetColumnType(16) != sql::ColumnType::kNull) {
     group.user_bidding_signals = load.ColumnString(16);
   }
-  group.ads = DeserializeInterestGroupAdVector(load.ColumnString(17),
-                                               /*for_components=*/false);
-  group.ad_components = DeserializeInterestGroupAdVector(
-      load.ColumnString(18), /*for_components=*/true);
+  group.ads = DeserializeInterestGroupAdVectorProto(load.ColumnString(17));
+  group.ad_components =
+      DeserializeInterestGroupAdVectorProto(load.ColumnString(18));
   group.ad_sizes = DeserializeStringSizeMap(load.ColumnString(19));
   group.size_groups = DeserializeStringStringVectorMap(load.ColumnString(20));
   group.auction_server_request_flags =
@@ -1637,8 +1813,8 @@
             "trusted_bidding_signals_url,"
             "trusted_bidding_signals_keys,"
             "user_bidding_signals,"  // opaque data
-            "ads,"
-            "ad_components,"
+            "ads_pb,"
+            "ad_components_pb,"
             "ad_sizes,"
             "size_groups,"
             "auction_server_request_flags,"
@@ -1675,8 +1851,8 @@
   } else {
     join_group.BindNull(20);
   }
-  join_group.BindString(21, Serialize(data.ads));
-  join_group.BindString(22, Serialize(data.ad_components));
+  join_group.BindBlob(21, Serialize(data.ads));
+  join_group.BindBlob(22, Serialize(data.ad_components));
   join_group.BindString(23, Serialize(data.ad_sizes));
   join_group.BindString(24, Serialize(data.size_groups));
   join_group.BindInt64(25, Serialize(data.auction_server_request_flags));
@@ -1714,8 +1890,8 @@
             "update_url=?,"
             "trusted_bidding_signals_url=?,"
             "trusted_bidding_signals_keys=?,"
-            "ads=?,"
-            "ad_components=?,"
+            "ads_pb=?,"
+            "ad_components_pb=?,"
             "ad_sizes=?,"
             "size_groups=?,"
             "auction_server_request_flags=?, "
@@ -1743,8 +1919,8 @@
   store_group.BindString(11, Serialize(group.update_url));
   store_group.BindString(12, Serialize(group.trusted_bidding_signals_url));
   store_group.BindString(13, Serialize(group.trusted_bidding_signals_keys));
-  store_group.BindString(14, Serialize(group.ads));
-  store_group.BindString(15, Serialize(group.ad_components));
+  store_group.BindBlob(14, Serialize(group.ads));
+  store_group.BindBlob(15, Serialize(group.ad_components));
   store_group.BindString(16, Serialize(group.ad_sizes));
   store_group.BindString(17, Serialize(group.size_groups));
   store_group.BindInt64(18, Serialize(group.auction_server_request_flags));
@@ -2758,8 +2934,8 @@
               "LENGTH(interest_groups.trusted_bidding_signals_url)+"
               "LENGTH(interest_groups.trusted_bidding_signals_keys)+"
               "LENGTH(interest_groups.user_bidding_signals)+"
-              "LENGTH(interest_groups.ads)+"
-              "LENGTH(interest_groups.ad_components)+"
+              "LENGTH(interest_groups.ads_pb)+"
+              "LENGTH(interest_groups.ad_components_pb)+"
               "LENGTH(interest_groups.ad_sizes)+"
               "LENGTH(interest_groups.size_groups)+"
               "LENGTH(interest_groups.additional_bid_key)+"
@@ -2980,7 +3156,7 @@
   }
 
   if (new_db) {
-    return CreateV15Schema(*db_);
+    return CreateV16Schema(*db_);
   }
 
   const int db_version = meta_table.GetVersionNumber();
@@ -3046,6 +3222,11 @@
         if (!UpgradeV14SchemaToV15(*db_, meta_table)) {
           return false;
         }
+        ABSL_FALLTHROUGH_INTENDED;
+      case 15:
+        if (!UpgradeV15SchemaToV16(*db_, meta_table)) {
+          return false;
+        }
 
         if (!meta_table.SetVersionNumber(kCurrentVersionNumber)) {
           return false;
@@ -3054,7 +3235,8 @@
     return transaction.Commit();
   }
 
-  NOTREACHED();  // Only V6 through V12 should have passed RazeIfIncompatible.
+  NOTREACHED();  // Only versions 6 up to the current version should have passed
+                 // RazeIfIncompatible.
   return false;
 }