pw_software_update: Use a user_manifest target file

Instead of embedding user metadata in the TargetsMetadata, a
reserved "user_manifest" target file is used to pass the user's
metadata as an opaque blob.

During verification this blob is handed to the backend to verify
when desired.

Requires: pigweed-internal:16500
Change-Id: I111d8cbbfb2a43b68baea0c4b0545e6005a0a9de
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/65361
Reviewed-by: David Rogers <davidrogers@google.com>
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_software_update/bundled_update_service.cc b/pw_software_update/bundled_update_service.cc
index 1f2cb0b..1f8bcde 100644
--- a/pw_software_update/bundled_update_service.cc
+++ b/pw_software_update/bundled_update_service.cc
@@ -63,6 +63,12 @@
   } while (false)
 
 namespace pw::software_update {
+namespace {
+
+constexpr std::string_view kTopLevelTargetsName = "targets";
+constexpr std::string_view kUserManifestTargetFileName = "user_manifest";
+
+}  // namespace
 
 Status BundledUpdateService::GetStatus(
     ServerContext&,
@@ -168,6 +174,19 @@
     bundle_open_ = true;
   }
 
+  // Have the backend verify the user_manifest if present.
+  stream::IntervalReader user_manifest =
+      bundle_.GetTargetPayload(kUserManifestTargetFileName);
+  if (user_manifest.ok()) {
+    const size_t bundle_offset = user_manifest.start();
+    if (!backend_.VerifyUserManifest(user_manifest, bundle_offset).ok()) {
+      std::lock_guard lock(mutex_);
+      SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
+                "Backend::VerifyUserManifest() failed");
+      return;
+    }
+  }
+
   // Notify backend we're done verifying.
   status = backend_.AfterBundleVerified();
   {
@@ -323,7 +342,6 @@
 
   // There should only be one element in the map, which is the top-level
   // targets metadata.
-  constexpr std::string_view kTopLevelTargetsName = "targets";
   protobuf::Message signed_targets_metadata =
       signed_targets_metadata_map[kTopLevelTargetsName];
   if (const Status status = signed_targets_metadata.status(); !status.ok()) {
@@ -413,6 +431,9 @@
     const std::string_view file_name_view(
         reinterpret_cast<const char*>(file_name_span.data()),
         file_name_span.size_bytes());
+    if (file_name_view.compare(kUserManifestTargetFileName) == 0) {
+      continue;  // user_manifest is not applied by the backend.
+    }
     stream::IntervalReader file_reader =
         bundle_.GetTargetPayload(file_name_view);
     const size_t bundle_offset = file_reader.start();
diff --git a/pw_software_update/public/pw_software_update/bundled_update_backend.h b/pw_software_update/public/pw_software_update/bundled_update_backend.h
index 9ead759..ba79d86 100644
--- a/pw_software_update/public/pw_software_update/bundled_update_backend.h
+++ b/pw_software_update/public/pw_software_update/bundled_update_backend.h
@@ -62,9 +62,11 @@
   virtual Status BeforeBundleVerify() { return OkStatus(); };
 
   // Perform any product-specific bundle verification tasks (e.g. hw version
-  // match check), done after TUF bundle verification process.
-  virtual Status VerifyMetadata(
-      [[maybe_unused]] const ManifestAccessor& manifest) {
+  // match check), done after TUF bundle verification process if user_manifest
+  // was provided as part of the bundle.
+  virtual Status VerifyUserManifest(
+      [[maybe_unused]] stream::Reader& user_manifest,
+      [[maybe_unused]] size_t update_bundle_offset) {
     return OkStatus();
   };