libsnapshot: Split CowReader into CowParserV2.

Remove format-specific logic from CowReader and split it out into a new
class called CowParserV2. To make reading the header easier, the
version and size bits are now in a separate CowHeaderPrefix struct.

Bug: 280529365
Test: apply OTA on CF
      inspect_cow
Change-Id: I29b5617ec094d4fb0c284485883d2e921a5bdbf8
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index d3bd904..cba6844 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -179,6 +179,7 @@
         "libsnapshot_cow/cow_writer.cpp",
         "libsnapshot_cow/cow_format.cpp",
         "libsnapshot_cow/cow_compress.cpp",
+        "libsnapshot_cow/parser_v2.cpp",
     ],
     host_supported: true,
     recovery_available: true,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c3ca00a..9f94699 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -52,13 +52,15 @@
 // between writing the last operation/data pair, or the footer itself. In this
 // case, the safest way to proceed is to assume the last operation is faulty.
 
-struct CowHeader {
+struct CowHeaderPrefix {
     uint64_t magic;
     uint16_t major_version;
     uint16_t minor_version;
+    uint16_t header_size;  // size of CowHeader.
+} __attribute__((packed));
 
-    // Size of this struct.
-    uint16_t header_size;
+struct CowHeader {
+    CowHeaderPrefix prefix;
 
     // Size of footer struct
     uint16_t footer_size;
@@ -88,7 +90,7 @@
     // the compression type of that data (see constants below).
     uint8_t compression;
 
-    // Length of Footer Data. Currently 64 for both checksums
+    // Length of Footer Data. Currently 64.
     uint16_t data_length;
 
     // The amount of file space used by Cow operations
@@ -98,14 +100,6 @@
     uint64_t num_ops;
 } __attribute__((packed));
 
-struct CowFooterData {
-    // SHA256 checksums of Footer op
-    uint8_t footer_checksum[32];
-
-    // SHA256 of the operation sequence.
-    uint8_t ops_checksum[32];
-} __attribute__((packed));
-
 // Cow operations are currently fixed-size entries, but this may change if
 // needed.
 struct CowOperation {
@@ -167,7 +161,7 @@
 
 struct CowFooter {
     CowFooterOperation op;
-    CowFooterData data;
+    uint8_t unused[64];
 } __attribute__((packed));
 
 struct ScratchMetadata {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index c7b83a8..4151b01 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -171,7 +171,7 @@
 
     uint64_t GetCowSize() override;
 
-    uint32_t GetCowVersion() { return header_.major_version; }
+    uint32_t GetCowVersion() { return header_.prefix.major_version; }
 
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
index edc9d65..8f80bb3 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
@@ -65,9 +65,9 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
     const auto& header = reader.GetHeader();
-    ASSERT_EQ(header.magic, kCowMagicNumber);
-    ASSERT_EQ(header.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
 
     CowFooter footer;
@@ -114,9 +114,9 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
     const auto& header = reader.GetHeader();
-    ASSERT_EQ(header.magic, kCowMagicNumber);
-    ASSERT_EQ(header.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
 
     CowFooter footer;
@@ -193,9 +193,9 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
     const auto& header = reader.GetHeader();
-    ASSERT_EQ(header.magic, kCowMagicNumber);
-    ASSERT_EQ(header.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
 
     CowFooter footer;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 6749cf1..c2a7fdb 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -30,7 +30,7 @@
 #include <zlib.h>
 
 #include "cow_decompress.h"
-#include "libsnapshot/cow_format.h"
+#include "parser_v2.h"
 
 namespace android {
 namespace snapshot {
@@ -43,15 +43,6 @@
       reader_flag_(reader_flag),
       is_merge_(is_merge) {}
 
-static void SHA256(const void*, size_t, uint8_t[]) {
-#if 0
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, data, length);
-    SHA256_Final(out, &c);
-#endif
-}
-
 std::unique_ptr<CowReader> CowReader::CloneCowReader() {
     auto cow = std::make_unique<CowReader>();
     cow->owned_fd_.reset();
@@ -63,7 +54,6 @@
     cow->merge_op_start_ = merge_op_start_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
-    cow->has_seq_ops_ = has_seq_ops_;
     cow->data_loc_ = data_loc_;
     cow->block_pos_index_ = block_pos_index_;
     cow->is_merge_ = is_merge_;
@@ -101,217 +91,26 @@
 bool CowReader::Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label) {
     fd_ = fd;
 
-    auto pos = lseek(fd_.get(), 0, SEEK_END);
-    if (pos < 0) {
-        PLOG(ERROR) << "lseek end failed";
-        return false;
-    }
-    fd_size_ = pos;
-
-    if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek header failed";
-        return false;
-    }
-    if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
-        PLOG(ERROR) << "read header failed";
+    if (!ReadCowHeader(fd, &header_)) {
         return false;
     }
 
-    if (header_.magic != kCowMagicNumber) {
-        LOG(ERROR) << "Header Magic corrupted. Magic: " << header_.magic
-                   << "Expected: " << kCowMagicNumber;
-        return false;
-    }
-    if (header_.footer_size != sizeof(CowFooter)) {
-        LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
-                   << sizeof(CowFooter);
-        return false;
-    }
-    if (header_.op_size != sizeof(CowOperation)) {
-        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
-        return false;
-    }
-    if (header_.cluster_ops == 1) {
-        LOG(ERROR) << "Clusters must contain at least two operations to function.";
-        return false;
-    }
-    if (header_.op_size != sizeof(CowOperation)) {
-        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
-        return false;
-    }
-    if (header_.cluster_ops == 1) {
-        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+    CowParserV2 parser;
+    if (!parser.Parse(fd, header_, label)) {
         return false;
     }
 
-    if ((header_.major_version > kCowVersionMajor) || (header_.minor_version != kCowVersionMinor)) {
-        LOG(ERROR) << "Header version mismatch";
-        LOG(ERROR) << "Major version: " << header_.major_version
-                   << "Expected: " << kCowVersionMajor;
-        LOG(ERROR) << "Minor version: " << header_.minor_version
-                   << "Expected: " << kCowVersionMinor;
-        return false;
-    }
+    footer_ = parser.footer();
+    fd_size_ = parser.fd_size();
+    last_label_ = parser.last_label();
+    ops_ = std::move(parser.ops());
+    data_loc_ = parser.data_loc();
 
-    if (!ParseOps(label)) {
-        return false;
-    }
     // If we're resuming a write, we're not ready to merge
     if (label.has_value()) return true;
     return PrepMergeOps();
 }
 
-bool CowReader::ParseOps(std::optional<uint64_t> label) {
-    uint64_t pos;
-    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
-
-    // Skip the scratch space
-    if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
-        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
-        size_t init_offset = header_.header_size + header_.buffer_size;
-        pos = lseek(fd_.get(), init_offset, SEEK_SET);
-        if (pos != init_offset) {
-            PLOG(ERROR) << "lseek ops failed";
-            return false;
-        }
-    } else {
-        pos = lseek(fd_.get(), header_.header_size, SEEK_SET);
-        if (pos != header_.header_size) {
-            PLOG(ERROR) << "lseek ops failed";
-            return false;
-        }
-        // Reading a v1 version of COW which doesn't have buffer_size.
-        header_.buffer_size = 0;
-    }
-    uint64_t data_pos = 0;
-
-    if (header_.cluster_ops) {
-        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
-    } else {
-        data_pos = pos + sizeof(CowOperation);
-    }
-
-    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
-    uint64_t current_op_num = 0;
-    uint64_t cluster_ops = header_.cluster_ops ?: 1;
-    bool done = false;
-
-    // Alternating op clusters and data
-    while (!done) {
-        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
-        if (to_add == 0) break;
-        ops_buffer->resize(current_op_num + to_add);
-        if (!android::base::ReadFully(fd_, &ops_buffer->data()[current_op_num],
-                                      to_add * sizeof(CowOperation))) {
-            PLOG(ERROR) << "read op failed";
-            return false;
-        }
-        // Parse current cluster to find start of next cluster
-        while (current_op_num < ops_buffer->size()) {
-            auto& current_op = ops_buffer->data()[current_op_num];
-            current_op_num++;
-            if (current_op.type == kCowXorOp) {
-                data_loc->insert({current_op.new_block, data_pos});
-            }
-            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
-            data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
-
-            if (current_op.type == kCowClusterOp) {
-                break;
-            } else if (current_op.type == kCowLabelOp) {
-                last_label_ = {current_op.source};
-
-                // If we reach the requested label, stop reading.
-                if (label && label.value() == current_op.source) {
-                    done = true;
-                    break;
-                }
-            } else if (current_op.type == kCowFooterOp) {
-                footer_.emplace();
-                CowFooter* footer = &footer_.value();
-                memcpy(&footer_->op, &current_op, sizeof(footer->op));
-                off_t offs = lseek(fd_.get(), pos, SEEK_SET);
-                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-                    PLOG(ERROR) << "lseek next op failed " << offs;
-                    return false;
-                }
-                if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
-                    LOG(ERROR) << "Could not read COW footer";
-                    return false;
-                }
-
-                // Drop the footer from the op stream.
-                current_op_num--;
-                done = true;
-                break;
-            } else if (current_op.type == kCowSequenceOp) {
-                has_seq_ops_ = true;
-            }
-        }
-
-        // Position for next cluster read
-        off_t offs = lseek(fd_.get(), pos, SEEK_SET);
-        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-            PLOG(ERROR) << "lseek next op failed " << offs;
-            return false;
-        }
-        ops_buffer->resize(current_op_num);
-    }
-
-    LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size();
-    // To successfully parse a COW file, we need either:
-    //  (1) a label to read up to, and for that label to be found, or
-    //  (2) a valid footer.
-    if (label) {
-        if (!last_label_) {
-            LOG(ERROR) << "Did not find label " << label.value()
-                       << " while reading COW (no labels found)";
-            return false;
-        }
-        if (last_label_.value() != label.value()) {
-            LOG(ERROR) << "Did not find label " << label.value()
-                       << ", last label=" << last_label_.value();
-            return false;
-        }
-    } else if (!footer_) {
-        LOG(ERROR) << "No COW footer found";
-        return false;
-    }
-
-    uint8_t csum[32];
-    memset(csum, 0, sizeof(uint8_t) * 32);
-
-    if (footer_) {
-        if (ops_buffer->size() != footer_->op.num_ops) {
-            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
-                       << ops_buffer->size();
-            return false;
-        }
-        if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
-            LOG(ERROR) << "ops size does not match ";
-            return false;
-        }
-        SHA256(&footer_->op, sizeof(footer_->op), footer_->data.footer_checksum);
-        if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
-            LOG(ERROR) << "ops checksum does not match";
-            return false;
-        }
-        SHA256(ops_buffer->data(), footer_->op.ops_size, csum);
-        if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
-            LOG(ERROR) << "ops checksum does not match";
-            return false;
-        }
-    }
-
-    ops_ = ops_buffer;
-    ops_->shrink_to_fit();
-    data_loc_ = data_loc;
-
-    return true;
-}
-
 //
 // This sets up the data needed for MergeOpIter. MergeOpIter presents
 // data in the order we intend to merge in.
@@ -446,7 +245,8 @@
             continue;
         }
 
-        if (!has_seq_ops_ && IsOrderedOp(current_op)) {
+        // Sequence ops must be the first ops in the stream.
+        if (seq_ops_set.empty() && IsOrderedOp(current_op)) {
             merge_op_blocks->emplace_back(current_op.new_block);
         } else if (seq_ops_set.count(current_op.new_block) == 0) {
             other_ops.push_back(current_op.new_block);
@@ -718,8 +518,8 @@
 
 bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
     // Validate the offset, taking care to acknowledge possible overflow of offset+len.
-    if (offset < header_.header_size || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
-        offset + len > fd_size_ - sizeof(CowFooter)) {
+    if (offset < header_.prefix.header_size || offset >= fd_size_ - sizeof(CowFooter) ||
+        len >= fd_size_ || offset + len > fd_size_ - sizeof(CowFooter)) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index 0e18979..1eaa038 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -186,10 +186,10 @@
 
 void CowWriter::SetupHeaders() {
     header_ = {};
-    header_.magic = kCowMagicNumber;
-    header_.major_version = kCowVersionMajor;
-    header_.minor_version = kCowVersionMinor;
-    header_.header_size = sizeof(CowHeader);
+    header_.prefix.magic = kCowMagicNumber;
+    header_.prefix.major_version = kCowVersionMajor;
+    header_.prefix.minor_version = kCowVersionMinor;
+    header_.prefix.header_size = sizeof(CowHeader);
     header_.footer_size = sizeof(CowFooter);
     header_.op_size = sizeof(CowOperation);
     header_.block_size = options_.block_size;
@@ -614,17 +614,6 @@
     return true;
 }
 
-// TODO: Fix compilation issues when linking libcrypto library
-// when snapuserd is compiled as part of ramdisk.
-static void SHA256(const void*, size_t, uint8_t[]) {
-#if 0
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, data, length);
-    SHA256_Final(out, &c);
-#endif
-}
-
 bool CowWriter::Finalize() {
     if (!FlushCluster()) {
         LOG(ERROR) << "Finalize: FlushCluster() failed";
@@ -665,10 +654,8 @@
         PLOG(ERROR) << "Failed to seek to footer position.";
         return false;
     }
-    memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
-    memset(&footer_.data.footer_checksum, 0, sizeof(uint8_t) * 32);
+    memset(&footer_.unused, 0, sizeof(footer_.unused));
 
-    SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
     // Write out footer at end of file
     if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&footer_),
                                    sizeof(footer_))) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 917cec4..c2c86ee 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -104,8 +104,9 @@
     if (reader.GetFooter(&footer)) has_footer = true;
 
     if (!opt.silent) {
-        std::cout << "Version: " << header.major_version << "." << header.minor_version << "\n";
-        std::cout << "Header size: " << header.header_size << "\n";
+        std::cout << "Version: " << header.prefix.major_version << "."
+                  << header.prefix.minor_version << "\n";
+        std::cout << "Header size: " << header.prefix.header_size << "\n";
         std::cout << "Footer size: " << header.footer_size << "\n";
         std::cout << "Block size: " << header.block_size << "\n";
         std::cout << "Merge ops: " << header.num_merge_ops << "\n";
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
new file mode 100644
index 0000000..fdb5c59
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -0,0 +1,238 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "parser_v2.h"
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::borrowed_fd;
+
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header) {
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+
+    memset(header, 0, sizeof(*header));
+
+    if (!android::base::ReadFully(fd, &header->prefix, sizeof(header->prefix))) {
+        return false;
+    }
+    if (header->prefix.magic != kCowMagicNumber) {
+        LOG(ERROR) << "Header Magic corrupted. Magic: " << header->prefix.magic
+                   << "Expected: " << kCowMagicNumber;
+        return false;
+    }
+    if (header->prefix.header_size > sizeof(CowHeader)) {
+        LOG(ERROR) << "Unknown CowHeader size (got " << header->prefix.header_size
+                   << " bytes, expected at most " << sizeof(CowHeader) << " bytes)";
+        return false;
+    }
+
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+    return android::base::ReadFully(fd, header, header->prefix.header_size);
+}
+
+bool CowParserV2::Parse(borrowed_fd fd, const CowHeader& header, std::optional<uint64_t> label) {
+    auto pos = lseek(fd.get(), 0, SEEK_END);
+    if (pos < 0) {
+        PLOG(ERROR) << "lseek end failed";
+        return false;
+    }
+    fd_size_ = pos;
+    header_ = header;
+
+    if (header_.footer_size != sizeof(CowFooter)) {
+        LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
+                   << sizeof(CowFooter);
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+
+    if ((header_.prefix.major_version > kCowVersionMajor) ||
+        (header_.prefix.minor_version != kCowVersionMinor)) {
+        LOG(ERROR) << "Header version mismatch, "
+                   << "major version: " << header_.prefix.major_version
+                   << ", expected: " << kCowVersionMajor
+                   << ", minor version: " << header_.prefix.minor_version
+                   << ", expected: " << kCowVersionMinor;
+        return false;
+    }
+
+    return ParseOps(fd, label);
+}
+
+bool CowParserV2::ParseOps(borrowed_fd fd, std::optional<uint64_t> label) {
+    uint64_t pos;
+    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
+
+    // Skip the scratch space
+    if (header_.prefix.major_version >= 2 && (header_.buffer_size > 0)) {
+        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
+        size_t init_offset = header_.prefix.header_size + header_.buffer_size;
+        pos = lseek(fd.get(), init_offset, SEEK_SET);
+        if (pos != init_offset) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+    } else {
+        pos = lseek(fd.get(), header_.prefix.header_size, SEEK_SET);
+        if (pos != header_.prefix.header_size) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+        // Reading a v1 version of COW which doesn't have buffer_size.
+        header_.buffer_size = 0;
+    }
+    uint64_t data_pos = 0;
+
+    if (header_.cluster_ops) {
+        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
+    } else {
+        data_pos = pos + sizeof(CowOperation);
+    }
+
+    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
+    uint64_t current_op_num = 0;
+    uint64_t cluster_ops = header_.cluster_ops ?: 1;
+    bool done = false;
+
+    // Alternating op clusters and data
+    while (!done) {
+        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
+        if (to_add == 0) break;
+        ops_buffer->resize(current_op_num + to_add);
+        if (!android::base::ReadFully(fd, &ops_buffer->data()[current_op_num],
+                                      to_add * sizeof(CowOperation))) {
+            PLOG(ERROR) << "read op failed";
+            return false;
+        }
+        // Parse current cluster to find start of next cluster
+        while (current_op_num < ops_buffer->size()) {
+            auto& current_op = ops_buffer->data()[current_op_num];
+            current_op_num++;
+            if (current_op.type == kCowXorOp) {
+                data_loc->insert({current_op.new_block, data_pos});
+            }
+            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
+            data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
+
+            if (current_op.type == kCowClusterOp) {
+                break;
+            } else if (current_op.type == kCowLabelOp) {
+                last_label_ = {current_op.source};
+
+                // If we reach the requested label, stop reading.
+                if (label && label.value() == current_op.source) {
+                    done = true;
+                    break;
+                }
+            } else if (current_op.type == kCowFooterOp) {
+                footer_.emplace();
+                CowFooter* footer = &footer_.value();
+                memcpy(&footer_->op, &current_op, sizeof(footer->op));
+                off_t offs = lseek(fd.get(), pos, SEEK_SET);
+                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+                    PLOG(ERROR) << "lseek next op failed " << offs;
+                    return false;
+                }
+                if (!android::base::ReadFully(fd, &footer->unused, sizeof(footer->unused))) {
+                    LOG(ERROR) << "Could not read COW footer";
+                    return false;
+                }
+
+                // Drop the footer from the op stream.
+                current_op_num--;
+                done = true;
+                break;
+            }
+        }
+
+        // Position for next cluster read
+        off_t offs = lseek(fd.get(), pos, SEEK_SET);
+        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+            PLOG(ERROR) << "lseek next op failed " << offs;
+            return false;
+        }
+        ops_buffer->resize(current_op_num);
+    }
+
+    LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size();
+    // To successfully parse a COW file, we need either:
+    //  (1) a label to read up to, and for that label to be found, or
+    //  (2) a valid footer.
+    if (label) {
+        if (!last_label_) {
+            LOG(ERROR) << "Did not find label " << label.value()
+                       << " while reading COW (no labels found)";
+            return false;
+        }
+        if (last_label_.value() != label.value()) {
+            LOG(ERROR) << "Did not find label " << label.value()
+                       << ", last label=" << last_label_.value();
+            return false;
+        }
+    } else if (!footer_) {
+        LOG(ERROR) << "No COW footer found";
+        return false;
+    }
+
+    uint8_t csum[32];
+    memset(csum, 0, sizeof(uint8_t) * 32);
+
+    if (footer_) {
+        if (ops_buffer->size() != footer_->op.num_ops) {
+            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
+                       << ops_buffer->size();
+            return false;
+        }
+        if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
+            LOG(ERROR) << "ops size does not match ";
+            return false;
+        }
+    }
+
+    ops_ = ops_buffer;
+    ops_->shrink_to_fit();
+    data_loc_ = data_loc;
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
new file mode 100644
index 0000000..afcf687
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_format.h>
+
+namespace android {
+namespace snapshot {
+
+class CowParserV2 {
+  public:
+    bool Parse(android::base::borrowed_fd fd, const CowHeader& header,
+               std::optional<uint64_t> label = {});
+
+    const CowHeader& header() const { return header_; }
+    const std::optional<CowFooter>& footer() const { return footer_; }
+    std::shared_ptr<std::vector<CowOperation>> ops() { return ops_; }
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc() const { return data_loc_; }
+    uint64_t fd_size() const { return fd_size_; }
+    const std::optional<uint64_t>& last_label() const { return last_label_; }
+
+  private:
+    bool ParseOps(android::base::borrowed_fd fd, std::optional<uint64_t> label);
+
+    CowHeader header_ = {};
+    std::optional<CowFooter> footer_;
+    std::shared_ptr<std::vector<CowOperation>> ops_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+    uint64_t fd_size_;
+    std::optional<uint64_t> last_label_;
+};
+
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header);
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index efa43b7..da9bd11 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -628,8 +628,8 @@
 bool Snapuserd::MmapMetadata() {
     const auto& header = reader_->GetHeader();
 
-    if (header.major_version >= 2 && header.buffer_size > 0) {
-        total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+    if (header.prefix.major_version >= 2 && header.buffer_size > 0) {
+        total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
         read_ahead_feature_ = true;
     } else {
         // mmap the first 4k page - older COW format
@@ -823,7 +823,7 @@
 uint64_t Snapuserd::GetBufferMetadataOffset() {
     const auto& header = reader_->GetHeader();
 
-    size_t size = header.header_size + sizeof(BufferState);
+    size_t size = header.prefix.header_size + sizeof(BufferState);
     return size;
 }
 
@@ -845,7 +845,7 @@
 size_t Snapuserd::GetBufferDataOffset() {
     const auto& header = reader_->GetHeader();
 
-    return (header.header_size + GetBufferMetadataSize());
+    return (header.prefix.header_size + GetBufferMetadataSize());
 }
 
 /*
@@ -862,7 +862,7 @@
     const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
-            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.prefix.header_size);
     return ra_state;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index a519639..c3343b8 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -240,9 +240,9 @@
 bool SnapshotHandler::MmapMetadata() {
     const auto& header = reader_->GetHeader();
 
-    total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+    total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
 
-    if (header.major_version >= 2 && header.buffer_size > 0) {
+    if (header.prefix.major_version >= 2 && header.buffer_size > 0) {
         scratch_space_ = true;
     }
 
@@ -362,7 +362,7 @@
 uint64_t SnapshotHandler::GetBufferMetadataOffset() {
     const auto& header = reader_->GetHeader();
 
-    return (header.header_size + sizeof(BufferState));
+    return (header.prefix.header_size + sizeof(BufferState));
 }
 
 /*
@@ -390,7 +390,7 @@
 size_t SnapshotHandler::GetBufferDataOffset() {
     const auto& header = reader_->GetHeader();
 
-    return (header.header_size + GetBufferMetadataSize());
+    return (header.prefix.header_size + GetBufferMetadataSize());
 }
 
 /*
@@ -413,7 +413,7 @@
     const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
-            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.prefix.header_size);
     return ra_state;
 }