[size] Add blob info into assembly manifest for product size checker

Sample Assembly manifest: https://paste.googleplex.com/5666137447596032
Bug: 100098

Change-Id: I10e1501472672ebc3828215ec7845577a5b79ba6
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/688023
Commit-Queue: Gopi Krishna Chitluri <gopichitluri@google.com>
Reviewed-by: Aidan Wolter <awolter@google.com>
diff --git a/src/developer/ffx/plugins/assembly/src/blobfs.rs b/src/developer/ffx/plugins/assembly/src/blobfs.rs
index e2610d9..9667f08 100644
--- a/src/developer/ffx/plugins/assembly/src/blobfs.rs
+++ b/src/developer/ffx/plugins/assembly/src/blobfs.rs
@@ -28,11 +28,11 @@
     // Add the base and cache packages.
     for package_manifest_path in &image_config.base {
         blobfs_builder.add_package(&package_manifest_path)?;
-        contents.packages.base.add_package(package_manifest_path)?;
+        contents.add_base_package(package_manifest_path)?;
     }
     for package_manifest_path in &image_config.cache {
         blobfs_builder.add_package(&package_manifest_path)?;
-        contents.packages.cache.add_package(package_manifest_path)?;
+        contents.add_cache_package(package_manifest_path)?;
     }
 
     // Add the base package and its contents.
diff --git a/src/developer/ffx/plugins/assembly/src/operations/size_check_product.rs b/src/developer/ffx/plugins/assembly/src/operations/size_check_product.rs
index e5dce86..fea87b1 100644
--- a/src/developer/ffx/plugins/assembly/src/operations/size_check_product.rs
+++ b/src/developer/ffx/plugins/assembly/src/operations/size_check_product.rs
@@ -100,10 +100,12 @@
                 base: PackageSetMetadata(vec![PackageMetadata {
                     name: "hello".to_string(),
                     manifest: "path".into(),
+                    blobs: Default::default(),
                 }]),
                 cache: PackageSetMetadata(vec![]),
             },
             maximum_contents_size: Some(1234),
+            blobs: Default::default(),
         };
         let mut images_manifest = ImagesManifest {
             images: vec![Image::VBMeta("a/b/c".into()), Image::FVM("x/y/z".into())],
diff --git a/src/lib/assembly/images_manifest/BUILD.gn b/src/lib/assembly/images_manifest/BUILD.gn
index 51e3283..5f4c5e5 100644
--- a/src/lib/assembly/images_manifest/BUILD.gn
+++ b/src/lib/assembly/images_manifest/BUILD.gn
@@ -15,6 +15,7 @@
       "//src/sys/pkg/lib/fuchsia-pkg",
       "//third_party/rust_crates:anyhow",
       "//third_party/rust_crates:serde",
+      "//third_party/rust_crates:tempfile",
     ]
     sources = [
       "src/images_manifest.rs",
diff --git a/src/lib/assembly/images_manifest/src/images_manifest.rs b/src/lib/assembly/images_manifest/src/images_manifest.rs
index 4cbf478..d06bd20 100644
--- a/src/lib/assembly/images_manifest/src/images_manifest.rs
+++ b/src/lib/assembly/images_manifest/src/images_manifest.rs
@@ -6,6 +6,7 @@
 use serde::de::{self, Deserializer};
 use serde::ser::Serializer;
 use serde::{Deserialize, Serialize};
+use std::collections::BTreeSet;
 use std::path::{Path, PathBuf};
 
 /// A manifest containing a list of images produced by the Image Assembler.
@@ -191,6 +192,52 @@
     pub packages: PackagesMetadata,
     /// Maximum total size of all the blobs stored in this image.
     pub maximum_contents_size: Option<u64>,
+    /// List of blobs across all packages
+    pub blobs: PackageSetBlobInfo,
+}
+
+#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
+#[serde(transparent)]
+pub struct PackageSetBlobInfo(BTreeSet<PackageBlob>);
+
+impl BlobfsContents {
+    /// Add base package info into BlobfsContents
+    pub fn add_base_package(&mut self, path: impl AsRef<Path>) -> anyhow::Result<()> {
+        Self::add_package(&mut self.packages.base, &mut self.blobs, path)?;
+        Ok(())
+    }
+
+    /// Add cache package info into BlobfsContents
+    pub fn add_cache_package(&mut self, path: impl AsRef<Path>) -> anyhow::Result<()> {
+        Self::add_package(&mut self.packages.cache, &mut self.blobs, path)?;
+        Ok(())
+    }
+
+    fn add_package(
+        package_set: &mut PackageSetMetadata,
+        content_blobs: &mut PackageSetBlobInfo,
+        path: impl AsRef<Path>,
+    ) -> anyhow::Result<()> {
+        let manifest = path.as_ref().to_owned();
+        let package_manifest = PackageManifest::try_load_from(&manifest)?;
+        let name = package_manifest.name().to_string();
+        let mut package_blobs: Vec<PackageBlob> = vec![];
+        for blob in package_manifest.into_blobs() {
+            content_blobs.0.insert(PackageBlob {
+                merkle: blob.merkle.to_string(),
+                path: blob.path.to_string(),
+                used_space_in_blobfs: 0, /* Use 0 until we populate with compressed size */
+            });
+            package_blobs.push(PackageBlob {
+                merkle: blob.merkle.to_string(),
+                path: blob.path.to_string(),
+                used_space_in_blobfs: 0, /* Use 0 until we populate with compressed size */
+            });
+        }
+        package_blobs.sort();
+        package_set.0.push(PackageMetadata { name, manifest, blobs: package_blobs });
+        Ok(())
+    }
 }
 
 /// Metadata on packages included in a given image.
@@ -207,16 +254,6 @@
 #[serde(transparent)]
 pub struct PackageSetMetadata(pub Vec<PackageMetadata>);
 
-impl PackageSetMetadata {
-    /// Add the package located at |path|.
-    pub fn add_package(&mut self, path: impl AsRef<Path>) -> anyhow::Result<()> {
-        let manifest = path.as_ref().to_owned();
-        let name = PackageManifest::try_load_from(&manifest)?.name().to_string();
-        self.0.push(PackageMetadata { name, manifest });
-        Ok(())
-    }
-}
-
 /// Metadata on a single package included in a given image.
 #[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
 pub struct PackageMetadata {
@@ -224,6 +261,18 @@
     pub name: String,
     /// Path to the package's manifest.
     pub manifest: PathBuf,
+    /// List of blobs in this package.
+    pub blobs: Vec<PackageBlob>,
+}
+
+#[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq, Deserialize, Serialize)]
+pub struct PackageBlob {
+    // Merkle hash of this blob
+    pub merkle: String,
+    // Path of blob in package
+    pub path: String,
+    // Space used by this blob in blobfs
+    pub used_space_in_blobfs: u64,
 }
 
 #[derive(Debug, Deserialize)]
@@ -289,6 +338,8 @@
 mod tests {
     use super::*;
     use serde_json::{json, Value};
+    use std::io::Write;
+    use tempfile::{tempdir, NamedTempFile};
 
     #[test]
     fn serialize() {
@@ -379,6 +430,84 @@
         assert!(result.unwrap_err().is_data());
     }
 
+    #[test]
+    fn test_blobfs_contents_add_base_or_cache_package() -> anyhow::Result<()> {
+        let content = generate_test_package_manifest_content();
+
+        let mut package_manifest_temp_file = NamedTempFile::new()?;
+        let dir = tempdir().unwrap();
+        write!(package_manifest_temp_file, "{}", content)?;
+        let path = package_manifest_temp_file.into_temp_path();
+        path.persist(dir.path().join("package_manifest_temp_file.json"))?;
+
+        let mut contents = BlobfsContents::default();
+        contents.add_base_package(dir.path().join("package_manifest_temp_file.json"))?;
+        contents.add_cache_package(dir.path().join("package_manifest_temp_file.json"))?;
+        let actual_package_blobs_base = &(contents.packages.base.0[0].blobs);
+        let actual_package_blobs_cache = &(contents.packages.cache.0[0].blobs);
+
+        let package_blob1 = PackageBlob {
+            merkle: "7ddff816740d5803358dd4478d8437585e8d5c984b4361817d891807a16ff581".to_string(),
+            path: "bin/def".to_string(),
+            used_space_in_blobfs: 0,
+        };
+
+        let package_blob2 = PackageBlob {
+            merkle: "8cb3466c6e66592c8decaeaa3e399652fbe71dad5c3df1a5e919743a33815567".to_string(),
+            path: "lib/ghi".to_string(),
+            used_space_in_blobfs: 0,
+        };
+
+        let package_blob3 = PackageBlob {
+            merkle: "eabdb84d26416c1821fd8972e0d835eedaf7468e5a9ebe01e5944462411aec70".to_string(),
+            path: "abc/".to_string(),
+            used_space_in_blobfs: 0,
+        };
+
+        let expected_blobs = vec![package_blob1, package_blob2, package_blob3];
+        // Verify if all 3 blobs are available and also if they are sorted.
+        assert_eq!(actual_package_blobs_base, &expected_blobs);
+        assert_eq!(actual_package_blobs_cache, &expected_blobs);
+
+        // Verify blobs in BlobfsContents
+        assert_eq!(contents.blobs.0.iter().map(|x| x.clone()).collect::<Vec<_>>(), expected_blobs);
+        Ok(())
+    }
+
+    fn generate_test_package_manifest_content() -> String {
+        let content = r#"{
+            "package": {
+                "name": "test_package",
+                "version": "0"
+            },
+            "blobs": [
+                {
+                    "path": "abc/",
+                    "merkle": "eabdb84d26416c1821fd8972e0d835eedaf7468e5a9ebe01e5944462411aec70",
+                    "size": 2048,
+                    "source_path": "../../blobs/eabdb84d26416c1821fd8972e0d835eedaf7468e5a9ebe01e5944462411aec78"
+                },
+                {
+                    "path": "bin/def",
+                    "merkle": "7ddff816740d5803358dd4478d8437585e8d5c984b4361817d891807a16ff581",
+                    "size": 188416,
+                    "source_path": "../../blobs/7ddff816740d5803358dd4478d8437585e8d5c984b4361817d891807a16ff581"
+                },
+                {
+                    "path": "lib/ghi",
+                    "merkle": "8cb3466c6e66592c8decaeaa3e399652fbe71dad5c3df1a5e919743a33815567",
+                    "size": 692224,
+                    "source_path": "../../blobs/8cb3466c6e66592c8decaeaa3e399652fbe71dad5c3df1a5e919743a33815567"
+                }
+            ],
+            "version": "1",
+            "blob_sources_relative": "file",
+            "repository": "fuchsia.com"
+        }
+        "#;
+        return content.to_string();
+    }
+
     fn generate_test_value() -> Value {
         json!([
             {
@@ -407,6 +536,7 @@
                         "cache": [],
                     },
                     "maximum_contents_size": None::<u64>,
+                    "blobs": [],
                 },
             },
             {