Allow parsing boot image with header version 3

The boot image header version no longer contains a page_size field;
and it assumes the block size is always 4096. Also, the order of the
header fields are different in version 3 from the previous versions.
While the position of "header_version" is fixed at position #9 across
all image headers.

This cl handles header version 3 separately from the previous versions.
Also create a stub implementation since the parsing of boot image is only
used in android.

Bug: 152371989
Test: unit tests, generate incremental OTA for the affected package, build with stub file
Change-Id: Iea2145e0c234216f9ee42c571bd53dc93b4b9d2e
diff --git a/Android.bp b/Android.bp
index 21ba507..e3116f5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -492,6 +492,9 @@
         "update_metadata-protos_exports",
     ],
 
+    header_libs: [
+        "bootimg_headers",
+    ],
     static_libs: [
         "libavb",
         "libbrotli",
diff --git a/payload_generator/boot_img_filesystem.cc b/payload_generator/boot_img_filesystem.cc
index 19de410..89b175e 100644
--- a/payload_generator/boot_img_filesystem.cc
+++ b/payload_generator/boot_img_filesystem.cc
@@ -17,6 +17,7 @@
 #include "update_engine/payload_generator/boot_img_filesystem.h"
 
 #include <base/logging.h>
+#include <bootimg.h>
 #include <brillo/secure_blob.h>
 #include <puffin/utils.h>
 
@@ -35,16 +36,61 @@
   if (filename.empty())
     return nullptr;
 
-  brillo::Blob header;
-  if (!utils::ReadFileChunk(filename, 0, sizeof(boot_img_hdr), &header) ||
-      header.size() != sizeof(boot_img_hdr) ||
-      memcmp(header.data(), BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0) {
+  if (brillo::Blob header_magic;
+      !utils::ReadFileChunk(filename, 0, BOOT_MAGIC_SIZE, &header_magic) ||
+      memcmp(header_magic.data(), BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0) {
+    return nullptr;
+  }
+
+  // The order of image header fields are different in version 3 from the
+  // previous versions. But the position of "header_version" is fixed at #9
+  // across all image headers.
+  // See details in system/tools/mkbootimg/include/bootimg/bootimg.h
+  constexpr size_t header_version_offset =
+      BOOT_MAGIC_SIZE + 8 * sizeof(uint32_t);
+  brillo::Blob header_version_blob;
+  if (!utils::ReadFileChunk(filename,
+                            header_version_offset,
+                            sizeof(uint32_t),
+                            &header_version_blob)) {
+    return nullptr;
+  }
+  uint32_t header_version =
+      *reinterpret_cast<uint32_t*>(header_version_blob.data());
+  if (header_version > 3) {
+    LOG(WARNING) << "Boot image header version " << header_version
+                 << " isn't supported for parsing";
+    return nullptr;
+  }
+
+  // Read the bytes of boot image header based on the header version.
+  size_t header_size =
+      header_version == 3 ? sizeof(boot_img_hdr_v3) : sizeof(boot_img_hdr_v0);
+  brillo::Blob header_blob;
+  if (!utils::ReadFileChunk(filename, 0, header_size, &header_blob)) {
     return nullptr;
   }
 
   unique_ptr<BootImgFilesystem> result(new BootImgFilesystem());
   result->filename_ = filename;
-  memcpy(&result->hdr_, header.data(), header.size());
+  if (header_version < 3) {
+    auto hdr_v0 = reinterpret_cast<boot_img_hdr_v0*>(header_blob.data());
+    CHECK_EQ(0, memcmp(hdr_v0->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE));
+    CHECK_LT(hdr_v0->header_version, 3u);
+    result->kernel_size_ = hdr_v0->kernel_size;
+    result->ramdisk_size_ = hdr_v0->ramdisk_size;
+    result->page_size_ = hdr_v0->page_size;
+  } else {
+    auto hdr_v3 = reinterpret_cast<boot_img_hdr_v3*>(header_blob.data());
+    CHECK_EQ(0, memcmp(hdr_v3->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE));
+    CHECK_EQ(3u, hdr_v3->header_version);
+    result->kernel_size_ = hdr_v3->kernel_size;
+    result->ramdisk_size_ = hdr_v3->ramdisk_size;
+    result->page_size_ = 4096;
+  }
+
+  CHECK_GT(result->page_size_, 0u);
+
   return result;
 }
 
@@ -87,13 +133,13 @@
   files->clear();
   const uint64_t file_size = utils::FileSize(filename_);
   // The first page is header.
-  uint64_t offset = hdr_.page_size;
-  if (hdr_.kernel_size > 0 && offset + hdr_.kernel_size <= file_size) {
-    files->emplace_back(GetFile("<kernel>", offset, hdr_.kernel_size));
+  uint64_t offset = page_size_;
+  if (kernel_size_ > 0 && offset + kernel_size_ <= file_size) {
+    files->emplace_back(GetFile("<kernel>", offset, kernel_size_));
   }
-  offset += utils::RoundUp(hdr_.kernel_size, hdr_.page_size);
-  if (hdr_.ramdisk_size > 0 && offset + hdr_.ramdisk_size <= file_size) {
-    files->emplace_back(GetFile("<ramdisk>", offset, hdr_.ramdisk_size));
+  offset += utils::RoundUp(kernel_size_, page_size_);
+  if (ramdisk_size_ > 0 && offset + ramdisk_size_ <= file_size) {
+    files->emplace_back(GetFile("<ramdisk>", offset, ramdisk_size_));
   }
   return true;
 }
diff --git a/payload_generator/boot_img_filesystem.h b/payload_generator/boot_img_filesystem.h
index 87725d4..61f755c 100644
--- a/payload_generator/boot_img_filesystem.h
+++ b/payload_generator/boot_img_filesystem.h
@@ -52,23 +52,9 @@
   // The boot.img file path.
   std::string filename_;
 
-// https://android.googlesource.com/platform/system/core/+/master/mkbootimg/include/bootimg/bootimg.h
-#define BOOT_MAGIC "ANDROID!"
-#define BOOT_MAGIC_SIZE 8
-  struct boot_img_hdr {
-    // Must be BOOT_MAGIC.
-    uint8_t magic[BOOT_MAGIC_SIZE];
-    uint32_t kernel_size;  /* size in bytes */
-    uint32_t kernel_addr;  /* physical load addr */
-    uint32_t ramdisk_size; /* size in bytes */
-    uint32_t ramdisk_addr; /* physical load addr */
-    uint32_t second_size;  /* size in bytes */
-    uint32_t second_addr;  /* physical load addr */
-    uint32_t tags_addr;    /* physical addr for kernel tags */
-    uint32_t page_size;    /* flash page size we assume */
-  } __attribute__((packed));
-  // The boot image header.
-  boot_img_hdr hdr_;
+  uint32_t kernel_size_;  /* size in bytes */
+  uint32_t ramdisk_size_; /* size in bytes */
+  uint32_t page_size_;    /* flash page size we assume */
 
   DISALLOW_COPY_AND_ASSIGN(BootImgFilesystem);
 };
diff --git a/payload_generator/boot_img_filesystem_stub.cc b/payload_generator/boot_img_filesystem_stub.cc
new file mode 100644
index 0000000..4928fa1
--- /dev/null
+++ b/payload_generator/boot_img_filesystem_stub.cc
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2020 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 "update_engine/payload_generator/boot_img_filesystem.h"
+
+namespace chromeos_update_engine {
+std::unique_ptr<BootImgFilesystem> BootImgFilesystem::CreateFromFile(
+    const std::string& /* filename */) {
+  return nullptr;
+}
+
+size_t BootImgFilesystem::GetBlockSize() const {
+  return 4096;
+}
+
+size_t BootImgFilesystem::GetBlockCount() const {
+  return 0;
+}
+
+FilesystemInterface::File BootImgFilesystem::GetFile(
+    const std::string& /* name */,
+    uint64_t /* offset */,
+    uint64_t /* size */) const {
+  return {};
+}
+
+bool BootImgFilesystem::GetFiles(std::vector<File>* /* files */) const {
+  return false;
+}
+
+bool BootImgFilesystem::LoadSettings(brillo::KeyValueStore* /* store */) const {
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/boot_img_filesystem_unittest.cc b/payload_generator/boot_img_filesystem_unittest.cc
index b1e0d99..0b115e0 100644
--- a/payload_generator/boot_img_filesystem_unittest.cc
+++ b/payload_generator/boot_img_filesystem_unittest.cc
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include <bootimg.h>
 #include <brillo/secure_blob.h>
 #include <gtest/gtest.h>
 
@@ -32,18 +33,32 @@
 class BootImgFilesystemTest : public ::testing::Test {
  protected:
   brillo::Blob GetBootImg(const brillo::Blob& kernel,
-                          const brillo::Blob& ramdisk) {
+                          const brillo::Blob& ramdisk,
+                          bool header_version3 = false) {
     brillo::Blob boot_img(16 * 1024);
-    BootImgFilesystem::boot_img_hdr hdr;
-    memcpy(hdr.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE);
-    hdr.kernel_size = kernel.size();
-    hdr.ramdisk_size = ramdisk.size();
-    hdr.page_size = 4096;
+    constexpr uint32_t page_size = 4096;
+
     size_t offset = 0;
-    memcpy(boot_img.data() + offset, &hdr, sizeof(hdr));
-    offset += utils::RoundUp(sizeof(hdr), hdr.page_size);
+    if (header_version3) {
+      boot_img_hdr_v3 hdr_v3{};
+      memcpy(hdr_v3.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE);
+      hdr_v3.kernel_size = kernel.size();
+      hdr_v3.ramdisk_size = ramdisk.size();
+      hdr_v3.header_version = 3;
+      memcpy(boot_img.data() + offset, &hdr_v3, sizeof(hdr_v3));
+      offset += utils::RoundUp(sizeof(hdr_v3), page_size);
+    } else {
+      boot_img_hdr_v0 hdr_v0{};
+      memcpy(hdr_v0.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE);
+      hdr_v0.kernel_size = kernel.size();
+      hdr_v0.ramdisk_size = ramdisk.size();
+      hdr_v0.page_size = page_size;
+      hdr_v0.header_version = 0;
+      memcpy(boot_img.data() + offset, &hdr_v0, sizeof(hdr_v0));
+      offset += utils::RoundUp(sizeof(hdr_v0), page_size);
+    }
     memcpy(boot_img.data() + offset, kernel.data(), kernel.size());
-    offset += utils::RoundUp(kernel.size(), hdr.page_size);
+    offset += utils::RoundUp(kernel.size(), page_size);
     memcpy(boot_img.data() + offset, ramdisk.data(), ramdisk.size());
     return boot_img;
   }
@@ -76,6 +91,31 @@
   EXPECT_TRUE(files[1].deflates.empty());
 }
 
+TEST_F(BootImgFilesystemTest, ImageHeaderVersion3) {
+  test_utils::WriteFileVector(
+      boot_file_.path(),
+      GetBootImg(brillo::Blob(1000, 'k'), brillo::Blob(5000, 'r'), true));
+  unique_ptr<BootImgFilesystem> fs =
+      BootImgFilesystem::CreateFromFile(boot_file_.path());
+  EXPECT_NE(nullptr, fs);
+
+  vector<FilesystemInterface::File> files;
+  EXPECT_TRUE(fs->GetFiles(&files));
+  ASSERT_EQ(2u, files.size());
+
+  EXPECT_EQ("<kernel>", files[0].name);
+  EXPECT_EQ(1u, files[0].extents.size());
+  EXPECT_EQ(1u, files[0].extents[0].start_block());
+  EXPECT_EQ(1u, files[0].extents[0].num_blocks());
+  EXPECT_TRUE(files[0].deflates.empty());
+
+  EXPECT_EQ("<ramdisk>", files[1].name);
+  EXPECT_EQ(1u, files[1].extents.size());
+  EXPECT_EQ(2u, files[1].extents[0].start_block());
+  EXPECT_EQ(2u, files[1].extents[0].num_blocks());
+  EXPECT_TRUE(files[1].deflates.empty());
+}
+
 TEST_F(BootImgFilesystemTest, BadImageTest) {
   brillo::Blob boot_img = GetBootImg({}, {});
   boot_img[7] = '?';