Introduce CreateBrotliSourceStreamWithDictionary() method

This CL introduces CreateBrotliSourceStreamWithDictionary() method which
calls BrotliDecoderAttachDictionary() method to use the dictionary.

This method will be used for compression dictionary transport with
Shared Brotli feature (crbug.com/1413922).

Bug: 1413922
Change-Id: Id2b0e690cd6f2a2c6fceaa61a9ec24566cdf017b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4624008
Reviewed-by: Adam Rice <ricea@chromium.org>
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1159496}
diff --git a/net/data/filter_unittests/google.sbr b/net/data/filter_unittests/google.sbr
new file mode 100644
index 0000000..695d0a0
--- /dev/null
+++ b/net/data/filter_unittests/google.sbr
Binary files differ
diff --git a/net/data/filter_unittests/test.dict b/net/data/filter_unittests/test.dict
new file mode 100644
index 0000000..4e004c2
--- /dev/null
+++ b/net/data/filter_unittests/test.dict
@@ -0,0 +1,7 @@
+When you visit www.google.com or one of the dozens of other Google domains, you'll be able to find information in many different languages; check stock quotes, maps, and news headlines; lookup phonebook listings for every city in the United States; search billions of images and peruse the world's largest archive of Usenet messages -- more than 1 billion posts dating back to 1981.
+
+We also provide ways to access all this information without making a special trip to the Google homepage. The Google Toolbar enables you to conduct a Google search from anywhere on the web. And for those times when you're away from your PC altogether, Google can be used from a number of wireless platforms including WAP and i-mode phones.
+
+Google's utility and ease of use have made it one of the world's best known brands almost entirely through word of mouth from satisfied users. As a business, Google generates revenue by providing advertisers with the opportunity to deliver measurable, cost-effective online advertising that is relevant to the information displayed on any given page. This makes the advertising useful to you as well as to the advertiser placing it. We believe you should know when someone has paid to put a message in front of you, so we always distinguish ads from the search results or other content on a page. We don't sell placement in the search results themselves, or allow people to pay for a higher ranking there.
+
+Thousands of advertisers use our Google AdWords program to promote their products and services on the web with targeted advertising, and we believe AdWords is the largest program of its kind. In addition, thousands of web site managers take advantage of our Google AdSense program to deliver ads relevant to the content on their sites, improving their ability to generate revenue and enhancing the experience for their users.
diff --git a/net/data/test_bundle_data.filelist b/net/data/test_bundle_data.filelist
index 65cf736..3eccfcb4 100644
--- a/net/data/test_bundle_data.filelist
+++ b/net/data/test_bundle_data.filelist
@@ -316,7 +316,9 @@
 data/embedded_test_server/mock-headers-without-crlf.html
 data/embedded_test_server/mock-headers-without-crlf.html.mock-http-headers
 data/filter_unittests/google.br
+data/filter_unittests/google.sbr
 data/filter_unittests/google.txt
+data/filter_unittests/test.dict
 data/name_constraints_unittest/directoryname-excludeall.pem
 data/name_constraints_unittest/directoryname-excluded.pem
 data/name_constraints_unittest/directoryname.pem
diff --git a/net/filter/brotli_source_stream.cc b/net/filter/brotli_source_stream.cc
index 72350ad..a5b04999 100644
--- a/net/filter/brotli_source_stream.cc
+++ b/net/filter/brotli_source_stream.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "net/filter/brotli_source_stream.h"
 
 #include "base/bit_cast.h"
@@ -11,6 +13,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "net/base/io_buffer.h"
 #include "third_party/brotli/include/brotli/decode.h"
+#include "third_party/brotli/include/brotli/shared_dictionary.h"
 
 namespace net {
 
@@ -22,10 +25,20 @@
 // Brotli format specification: http://www.ietf.org/id/draft-alakuijala-brotli.
 class BrotliSourceStream : public FilterSourceStream {
  public:
-  explicit BrotliSourceStream(std::unique_ptr<SourceStream> upstream)
-      : FilterSourceStream(SourceStream::TYPE_BROTLI, std::move(upstream)) {
+  explicit BrotliSourceStream(std::unique_ptr<SourceStream> upstream,
+                              scoped_refptr<IOBuffer> dictionary = nullptr,
+                              size_t dictionary_size = 0u)
+      : FilterSourceStream(SourceStream::TYPE_BROTLI, std::move(upstream)),
+        dictionary_(std::move(dictionary)),
+        dictionary_size_(dictionary_size) {
     brotli_state_ =
         BrotliDecoderCreateInstance(AllocateMemory, FreeMemory, this);
+    if (dictionary_) {
+      BROTLI_BOOL result = BrotliDecoderAttachDictionary(
+          brotli_state_, BROTLI_SHARED_DICTIONARY_RAW, dictionary_size_,
+          reinterpret_cast<const unsigned char*>(dictionary_->data()));
+      CHECK(result);
+    }
     CHECK(brotli_state_);
   }
 
@@ -163,6 +176,9 @@
     free(&array[-1]);
   }
 
+  const scoped_refptr<IOBuffer> dictionary_;
+  const size_t dictionary_size_;
+
   raw_ptr<BrotliDecoderState, DanglingUntriaged> brotli_state_;
 
   DecodingStatus decoding_status_ = DecodingStatus::DECODING_IN_PROGRESS;
@@ -180,4 +196,12 @@
   return std::make_unique<BrotliSourceStream>(std::move(previous));
 }
 
+std::unique_ptr<FilterSourceStream> CreateBrotliSourceStreamWithDictionary(
+    std::unique_ptr<SourceStream> previous,
+    scoped_refptr<IOBuffer> dictionary,
+    size_t dictionary_size) {
+  return std::make_unique<BrotliSourceStream>(
+      std::move(previous), std::move(dictionary), dictionary_size);
+}
+
 }  // namespace net
diff --git a/net/filter/brotli_source_stream.h b/net/filter/brotli_source_stream.h
index 5ff3caf..8d7915d9 100644
--- a/net/filter/brotli_source_stream.h
+++ b/net/filter/brotli_source_stream.h
@@ -7,6 +7,8 @@
 
 #include <memory>
 
+#include "base/memory/scoped_refptr.h"
+#include "net/base/io_buffer.h"
 #include "net/base/net_export.h"
 #include "net/filter/filter_source_stream.h"
 #include "net/filter/source_stream.h"
@@ -16,6 +18,11 @@
 NET_EXPORT_PRIVATE std::unique_ptr<FilterSourceStream> CreateBrotliSourceStream(
     std::unique_ptr<SourceStream> upstream);
 
+NET_EXPORT_PRIVATE std::unique_ptr<FilterSourceStream>
+CreateBrotliSourceStreamWithDictionary(std::unique_ptr<SourceStream> upstream,
+                                       scoped_refptr<IOBuffer> dictionary,
+                                       size_t dictionary_size);
+
 }  // namespace net
 
 #endif  // NET_FILTER_BROTLI_SOURCE_STREAM_H_
diff --git a/net/filter/brotli_source_stream_disabled.cc b/net/filter/brotli_source_stream_disabled.cc
index 4fc84457..090a04d 100644
--- a/net/filter/brotli_source_stream_disabled.cc
+++ b/net/filter/brotli_source_stream_disabled.cc
@@ -11,4 +11,11 @@
   return nullptr;
 }
 
+std::unique_ptr<FilterSourceStream> CreateBrotliSourceStreamWithDictionary(
+    std::unique_ptr<SourceStream> previous,
+    scoped_refptr<IOBuffer> dictionary,
+    size_t dictionary_size) {
+  return nullptr;
+}
+
 }  // namespace net
diff --git a/net/filter/brotli_source_stream_fuzzer.cc b/net/filter/brotli_source_stream_fuzzer.cc
index c2c5c65..a6ee109f 100644
--- a/net/filter/brotli_source_stream_fuzzer.cc
+++ b/net/filter/brotli_source_stream_fuzzer.cc
@@ -18,10 +18,25 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   net::TestCompletionCallback callback;
   FuzzedDataProvider data_provider(data, size);
-  auto fuzzed_source_stream =
-      std::make_unique<net::FuzzedSourceStream>(&data_provider);
-  std::unique_ptr<net::SourceStream> brotli_stream =
-      net::CreateBrotliSourceStream(std::move(fuzzed_source_stream));
+
+  const bool is_shared_dictionary = data_provider.ConsumeBool();
+  std::unique_ptr<net::SourceStream> brotli_stream;
+
+  if (is_shared_dictionary) {
+    const std::string dictionary = data_provider.ConsumeRandomLengthString();
+    scoped_refptr<net::IOBuffer> dictionary_buffer =
+        base::MakeRefCounted<net::StringIOBuffer>(dictionary);
+    auto fuzzed_source_stream =
+        std::make_unique<net::FuzzedSourceStream>(&data_provider);
+    brotli_stream = net::CreateBrotliSourceStreamWithDictionary(
+        std::move(fuzzed_source_stream), dictionary_buffer, dictionary.size());
+  } else {
+    auto fuzzed_source_stream =
+        std::make_unique<net::FuzzedSourceStream>(&data_provider);
+    brotli_stream =
+        net::CreateBrotliSourceStream(std::move(fuzzed_source_stream));
+  }
+
   while (true) {
     scoped_refptr<net::IOBufferWithSize> io_buffer =
         base::MakeRefCounted<net::IOBufferWithSize>(64);
diff --git a/net/filter/brotli_source_stream_unittest.cc b/net/filter/brotli_source_stream_unittest.cc
index 96a8953..e9686af 100644
--- a/net/filter/brotli_source_stream_unittest.cc
+++ b/net/filter/brotli_source_stream_unittest.cc
@@ -25,6 +25,16 @@
 const size_t kDefaultBufferSize = 4096;
 const size_t kSmallBufferSize = 128;
 
+// Get the path of data directory.
+base::FilePath GetTestDataDir() {
+  base::FilePath data_dir;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir);
+  data_dir = data_dir.AppendASCII("net");
+  data_dir = data_dir.AppendASCII("data");
+  data_dir = data_dir.AppendASCII("filter_unittests");
+  return data_dir;
+}
+
 }  // namespace
 
 class BrotliSourceStreamTest : public PlatformTest {
@@ -33,11 +43,7 @@
     PlatformTest::SetUp();
 
     // Get the path of data directory.
-    base::FilePath data_dir;
-    base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir);
-    data_dir = data_dir.AppendASCII("net");
-    data_dir = data_dir.AppendASCII("data");
-    data_dir = data_dir.AppendASCII("filter_unittests");
+    base::FilePath data_dir = GetTestDataDir();
 
     // Read data from the original file into buffer.
     base::FilePath file_path;
@@ -350,4 +356,43 @@
   EXPECT_EQ("BROTLI", brotli_stream()->Description());
 }
 
+TEST_F(BrotliSourceStreamTest, WithDictionary) {
+  std::string encoded_buffer;
+  std::string dictionary_data;
+
+  base::FilePath data_dir = GetTestDataDir();
+  // Read data from the encoded file into buffer.
+  base::FilePath encoded_file_path;
+  encoded_file_path = data_dir.AppendASCII("google.sbr");
+  ASSERT_TRUE(base::ReadFileToString(encoded_file_path, &encoded_buffer));
+
+  // Read data from the dictionary file into buffer.
+  base::FilePath dictionary_file_path;
+  dictionary_file_path = data_dir.AppendASCII("test.dict");
+  ASSERT_TRUE(base::ReadFileToString(dictionary_file_path, &dictionary_data));
+
+  scoped_refptr<net::IOBuffer> dictionary_buffer =
+      base::MakeRefCounted<net::StringIOBuffer>(dictionary_data);
+
+  scoped_refptr<IOBufferWithSize> out_buffer =
+      base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize);
+
+  auto source = std::make_unique<MockSourceStream>();
+  source->AddReadResult(encoded_buffer.c_str(), encoded_buffer.size(), OK,
+                        MockSourceStream::SYNC);
+
+  std::unique_ptr<SourceStream> brotli_stream =
+      CreateBrotliSourceStreamWithDictionary(
+          std::move(source), dictionary_buffer, dictionary_data.size());
+
+  TestCompletionCallback callback;
+  int bytes_read = brotli_stream->Read(out_buffer.get(), kDefaultBufferSize,
+                                       callback.callback());
+
+  EXPECT_EQ(static_cast<int>(source_data_len()), bytes_read);
+  EXPECT_EQ(
+      0, memcmp(out_buffer->data(), source_data().c_str(), source_data_len()));
+  EXPECT_EQ("BROTLI", brotli_stream->Description());
+}
+
 }  // namespace net