libsnapshot: Add support for brotli compression.

Bug: 162274240
Test: cow_api_test
Change-Id: I0b0ceec3c3041a6aea4b1e6c4d01ed0a8860d7e8
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index bdf1da6..95fbab8 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -152,6 +152,7 @@
         "liblog",
     ],
     static_libs: [
+        "libbrotli",
         "libz",
     ],
     ramdisk_available: true,
@@ -362,10 +363,11 @@
 
     static_libs: [
         "libbase",
+        "libbrotli",
         "liblog",
         "libdm",
-	"libz",
-	"libsnapshot_cow",
+        "libz",
+        "libsnapshot_cow",
     ],
 }
 
@@ -403,6 +405,7 @@
         "libz",
     ],
     static_libs: [
+        "libbrotli",
         "libgtest",
         "libsnapshot_cow",
     ],
@@ -494,11 +497,12 @@
     shared_libs: [
         "libbase",
         "liblog",
-        "libz",
     ],
     static_libs: [
+        "libbrotli",
         "libgtest",
         "libsnapshot_cow",
+        "libz",
     ],
     header_libs: [
         "libstorage_literals_headers",
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index d98fe59..ec206ad 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -30,12 +30,12 @@
 
 class CowTest : public ::testing::Test {
   protected:
-    void SetUp() override {
+    virtual void SetUp() override {
         cow_ = std::make_unique<TemporaryFile>();
         ASSERT_GE(cow_->fd, 0) << strerror(errno);
     }
 
-    void TearDown() override { cow_ = nullptr; }
+    virtual void TearDown() override { cow_ = nullptr; }
 
     std::unique_ptr<TemporaryFile> cow_;
 };
@@ -211,9 +211,11 @@
     void* GetBuffer(size_t, size_t* actual) override { return StringSink::GetBuffer(1, actual); }
 };
 
-TEST_F(CowTest, HorribleSink) {
+class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {};
+
+TEST_P(CompressionTest, HorribleSink) {
     CowOptions options;
-    options.compression = "gz";
+    options.compression = GetParam();
     CowWriter writer(options);
 
     ASSERT_TRUE(writer.Initialize(cow_->fd));
@@ -239,6 +241,8 @@
     ASSERT_EQ(sink.stream(), data);
 }
 
+INSTANTIATE_TEST_SUITE_P(CowApi, CompressionTest, testing::Values("none", "gz", "brotli"));
+
 TEST_F(CowTest, GetSize) {
     CowOptions options;
     CowWriter writer(options);
diff --git a/fs_mgr/libsnapshot/cow_decompress.cpp b/fs_mgr/libsnapshot/cow_decompress.cpp
index f480b85..faceafe 100644
--- a/fs_mgr/libsnapshot/cow_decompress.cpp
+++ b/fs_mgr/libsnapshot/cow_decompress.cpp
@@ -19,6 +19,7 @@
 #include <utility>
 
 #include <android-base/logging.h>
+#include <brotli/decode.h>
 #include <zlib.h>
 
 namespace android {
@@ -207,5 +208,57 @@
     return std::unique_ptr<IDecompressor>(new GzDecompressor());
 }
 
+class BrotliDecompressor final : public StreamDecompressor {
+  public:
+    ~BrotliDecompressor();
+
+    bool Init() override;
+    bool DecompressInput(const uint8_t* data, size_t length) override;
+    bool Done() override { return BrotliDecoderIsFinished(decoder_); }
+
+  private:
+    BrotliDecoderState* decoder_ = nullptr;
+};
+
+bool BrotliDecompressor::Init() {
+    decoder_ = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+    return true;
+}
+
+BrotliDecompressor::~BrotliDecompressor() {
+    if (decoder_) {
+        BrotliDecoderDestroyInstance(decoder_);
+    }
+}
+
+bool BrotliDecompressor::DecompressInput(const uint8_t* data, size_t length) {
+    size_t available_in = length;
+    const uint8_t* next_in = data;
+
+    bool needs_more_output = false;
+    while (available_in || needs_more_output) {
+        if (!output_buffer_remaining_ && !GetFreshBuffer()) {
+            return false;
+        }
+
+        auto output_buffer = output_buffer_;
+        auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in,
+                                               &output_buffer_remaining_, &output_buffer_, nullptr);
+        if (r == BROTLI_DECODER_RESULT_ERROR) {
+            LOG(ERROR) << "brotli decode failed";
+            return false;
+        }
+        if (!sink_->ReturnData(output_buffer, output_buffer_ - output_buffer)) {
+            return false;
+        }
+        needs_more_output = (r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
+    }
+    return true;
+}
+
+std::unique_ptr<IDecompressor> IDecompressor::Brotli() {
+    return std::unique_ptr<IDecompressor>(new BrotliDecompressor());
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/cow_decompress.h b/fs_mgr/libsnapshot/cow_decompress.h
index 1c8c40d..f485256 100644
--- a/fs_mgr/libsnapshot/cow_decompress.h
+++ b/fs_mgr/libsnapshot/cow_decompress.h
@@ -40,6 +40,7 @@
     // Factory methods for decompression methods.
     static std::unique_ptr<IDecompressor> Uncompressed();
     static std::unique_ptr<IDecompressor> Gz();
+    static std::unique_ptr<IDecompressor> Brotli();
 
     // |output_bytes| is the expected total number of bytes to sink.
     virtual bool Decompress(size_t output_bytes) = 0;
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 1aea3a9..720ee3b 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -233,6 +233,9 @@
         case kCowCompressGz:
             decompressor = IDecompressor::Gz();
             break;
+        case kCowCompressBrotli:
+            decompressor = IDecompressor::Brotli();
+            break;
         default:
             LOG(ERROR) << "Unknown compression type: " << op.compression;
             return false;
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 76238c2..f05f9ba 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -22,6 +22,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
+#include <brotli/encode.h>
 #include <libsnapshot/cow_writer.h>
 #include <zlib.h>
 
@@ -63,6 +64,10 @@
 
     if (options_.compression == "gz") {
         compression_ = kCowCompressGz;
+    } else if (options_.compression == "brotli") {
+        compression_ = kCowCompressBrotli;
+    } else if (options_.compression == "none") {
+        compression_ = kCowCompressNone;
     } else if (!options_.compression.empty()) {
         LOG(ERROR) << "unrecognized compression: " << options_.compression;
         return false;
@@ -171,6 +176,24 @@
             }
             return std::basic_string<uint8_t>(buffer.get(), dest_len);
         }
+        case kCowCompressBrotli: {
+            auto bound = BrotliEncoderMaxCompressedSize(length);
+            if (!bound) {
+                LOG(ERROR) << "BrotliEncoderMaxCompressedSize returned 0";
+                return {};
+            }
+            auto buffer = std::make_unique<uint8_t[]>(bound);
+
+            size_t encoded_size = bound;
+            auto rv = BrotliEncoderCompress(
+                    BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, length,
+                    reinterpret_cast<const uint8_t*>(data), &encoded_size, buffer.get());
+            if (!rv) {
+                LOG(ERROR) << "BrotliEncoderCompress failed";
+                return {};
+            }
+            return std::basic_string<uint8_t>(buffer.get(), encoded_size);
+        }
         default:
             LOG(ERROR) << "unhandled compression type: " << compression_;
             break;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 6d500e7..0adce48 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -98,6 +98,7 @@
 
 static constexpr uint8_t kCowCompressNone = 0;
 static constexpr uint8_t kCowCompressGz = 1;
+static constexpr uint8_t kCowCompressBrotli = 2;
 
 }  // namespace snapshot
 }  // namespace android