pw_software_udpate: Write user_manifest

Extend UpdateBundleAccessor:PersistManifest() method to write
user_manifest file if the bundle contains one.

Bug: 456
Change-Id: Ib22779d82ce288d43750959388f926fc9842543d
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/74024
Reviewed-by: Zihan Chen <zihanchen@google.com>
Reviewed-by: Ali Zhang <alizhang@google.com>
Commit-Queue: Yecheng Zhao <zyecheng@google.com>
diff --git a/pw_software_update/bundled_update_service.cc b/pw_software_update/bundled_update_service.cc
index 1bbfca5..d61d297 100644
--- a/pw_software_update/bundled_update_service.cc
+++ b/pw_software_update/bundled_update_service.cc
@@ -50,7 +50,6 @@
 namespace {
 
 constexpr std::string_view kTopLevelTargetsName = "targets";
-constexpr std::string_view kUserManifestTargetFileName = "user_manifest";
 
 }  // namespace
 
diff --git a/pw_software_update/public/pw_software_update/update_bundle_accessor.h b/pw_software_update/public/pw_software_update/update_bundle_accessor.h
index aaf72bd..f540046 100644
--- a/pw_software_update/public/pw_software_update/update_bundle_accessor.h
+++ b/pw_software_update/public/pw_software_update/update_bundle_accessor.h
@@ -25,6 +25,8 @@
 
 namespace pw::software_update {
 
+constexpr std::string_view kUserManifestTargetFileName = "user_manifest";
+
 // UpdateBundleAccessor is responsible for parsing, verifying and providing
 // target payload access of a software update bundle. It takes the following as
 // inputs:
diff --git a/pw_software_update/py/pw_software_update/generate_test_bundle.py b/pw_software_update/py/pw_software_update/generate_test_bundle.py
index 26ef2b1..b52a0cf 100644
--- a/pw_software_update/py/pw_software_update/generate_test_bundle.py
+++ b/pw_software_update/py/pw_software_update/generate_test_bundle.py
@@ -73,9 +73,12 @@
 TEST_ROOT_VERSION = 2
 TEST_TARGETS_VERSION = 2
 
+USER_MANIFEST_FILE_NAME = 'user_manifest'
+
 TARGET_FILES = {
     'file1': 'file 1 content'.encode(),
     'file2': 'file 2 content'.encode(),
+    USER_MANIFEST_FILE_NAME: 'user manfiest content'.encode(),
 }
 
 
@@ -230,6 +233,8 @@
         manifest = Manifest()
         manifest.targets_metadata['targets'].CopyFrom(
             self.generate_targets_metadata())
+        if USER_MANIFEST_FILE_NAME in self._payloads:
+            manifest.user_manifest = self._payloads[USER_MANIFEST_FILE_NAME]
         return manifest
 
 
diff --git a/pw_software_update/update_bundle_accessor.cc b/pw_software_update/update_bundle_accessor.cc
index 1413a5d..b35603e 100644
--- a/pw_software_update/update_bundle_accessor.cc
+++ b/pw_software_update/update_bundle_accessor.cc
@@ -431,7 +431,7 @@
   stream::IntervalReader metadata_reader = metadata.GetBytesReader();
 
   std::byte stream_pipe_buffer[WRITE_MANIFEST_STREAM_PIPE_BUFFER_SIZE];
-  return protobuf::WriteProtoStringToBytesMapEntry(
+  PW_TRY(protobuf::WriteProtoStringToBytesMapEntry(
       static_cast<uint32_t>(
           pw::software_update::Manifest::Fields::TARGETS_METADATA),
       name_reader,
@@ -439,7 +439,25 @@
       metadata_reader,
       metadata_reader.interval_size(),
       stream_pipe_buffer,
-      staged_manifest_writer);
+      staged_manifest_writer));
+
+  // Write `user_manifest` file if there is one.
+  Result<bool> user_manifest_exists =
+      IsTargetPayloadIncluded(kUserManifestTargetFileName);
+  PW_TRY(user_manifest_exists);
+  if (user_manifest_exists.value()) {
+    stream::IntervalReader user_manifest_reader =
+        GetTargetPayload(kUserManifestTargetFileName);
+    PW_TRY(user_manifest_reader.status());
+    protobuf::StreamEncoder encoder(staged_manifest_writer, {});
+    PW_TRY(encoder.WriteBytesFromStream(
+        static_cast<uint32_t>(Manifest::Fields::USER_MANIFEST),
+        user_manifest_reader,
+        user_manifest_reader.interval_size(),
+        stream_pipe_buffer));
+  }
+
+  return OkStatus();
 }
 
 Status UpdateBundleAccessor::Close() {