pw_software_update: land initial service implementation

Also renames the pw_software_update proto package to instead
use the canonical pw.software_update. As part of this the
Manifest and UpdateBundle C++ classes were renamed to
ManifestAccessor and UpdateBundleAccessor.

Moves the service.proto to bundled_update.proto and renames the source
files accordingly.

No-Docs-Update-Reason: Module still in early development
Requires: pigweed-internal:15503
Change-Id: I6531ee5772c17331e9c5ce7e16f4b72002656834
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/61960
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Reviewed-by: David Rogers <davidrogers@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_software_update/BUILD.gn b/pw_software_update/BUILD.gn
index d86d01f..fe8a6a6 100644
--- a/pw_software_update/BUILD.gn
+++ b/pw_software_update/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
+import("$dir_pw_third_party/nanopb/nanopb.gni")
 import("$dir_pw_third_party/protobuf/protobuf.gni")
 import("$dir_pw_unit_test/test.gni")
 
@@ -31,7 +32,7 @@
       "$dir_pw_third_party/protobuf:wellknown_types",
     ]
     sources = [
-      "service.proto",
+      "bundled_update.proto",
       "tuf.proto",
       "update_bundle.proto",
     ]
@@ -43,7 +44,7 @@
   pw_proto_library("protos") {
     deps = [ "$dir_pw_protobuf:common_protos" ]
     sources = [
-      "service.proto",
+      "bundled_update.proto",
       "tuf.proto",
       "update_bundle.proto",
     ]
@@ -69,28 +70,29 @@
   public = [
     "public/pw_software_update/bundled_update_backend.h",
     "public/pw_software_update/config.h",
-    "public/pw_software_update/manifest.h",
-    "public/pw_software_update/update_bundle.h",
+    "public/pw_software_update/manifest_accessor.h",
+    "public/pw_software_update/update_bundle_accessor.h",
   ]
   deps = [
     ":protos.pwpb",
     dir_pw_log,
   ]
-  sources = [ "update_bundle.cc" ]
+  sources = [ "update_bundle_accessor.cc" ]
 }
 
 if (dir_pw_third_party_nanopb != "" && dir_pw_third_party_protobuf != "") {
-  pw_source_set("rpc_service") {
+  pw_source_set("bundled_update_service") {
     public_configs = [ ":public_include_path" ]
-    public_deps = [ ":protos.nanopb_rpc" ]
-    deps = [ dir_pw_log ]
-    sources = [
-      "public/pw_software_update/service.h",
-      "service.cc",
+    public_deps = [
+      ":protos.nanopb_rpc",
+      ":update_manager",
     ]
+    deps = [ dir_pw_log ]
+    public = [ "public/pw_software_update/bundled_update_service.h" ]
+    sources = [ "bundled_update_service.cc" ]
   }
 } else {
-  group("rpc_service") {
+  group("bundled_update_service") {
   }
 }
 
@@ -115,8 +117,8 @@
       dir_pw_third_party_nanopb != "" && dir_pw_third_party_protobuf != ""
   sources = [ "update_bundle_test.cc" ]
   public_deps = [
+    ":bundled_update_service",
     ":generate_test_bundle",
-    ":rpc_service",
     ":update_bundle",
     "$dir_pw_kvs:fake_flash",
     "$dir_pw_kvs:fake_flash_test_key_value_store",
@@ -125,5 +127,23 @@
 }
 
 pw_test_group("tests") {
-  tests = [ ":update_bundle_test" ]
+  tests = [
+    ":update_bundle_test",
+    ":update_manager_test",
+  ]
+}
+
+pw_test("update_manager_test") {
+  enable_if =
+      dir_pw_third_party_nanopb != "" && dir_pw_third_party_protobuf != ""
+  sources = [ "update_manager_test.cc" ]
+  public_deps = [ ":update_manager" ]
+}
+
+pw_source_set("update_manager") {
+  public_configs = [ ":public_include_path" ]
+  public_deps = [ ":update_bundle" ]
+  deps = [ ":protos.pwpb" ]
+  public = [ "public/pw_software_update/update_manager.h" ]
+  sources = [ "update_manager.cc" ]
 }
diff --git a/pw_software_update/service.proto b/pw_software_update/bundled_update.proto
similarity index 75%
rename from pw_software_update/service.proto
rename to pw_software_update/bundled_update.proto
index 3765fc0..39e042e 100644
--- a/pw_software_update/service.proto
+++ b/pw_software_update/bundled_update.proto
@@ -13,7 +13,7 @@
 // the License.
 syntax = "proto3";
 
-package pw_software_update;
+package pw.software_update;
 
 import "pw_software_update/tuf.proto";
 import "pw_software_update/update_bundle.proto";
@@ -44,11 +44,11 @@
 
 message PrepareUpdateResult {
   OperationResult result = 1;
-  optional uint32 transfer_endpoint = 2;
+  optional uint32 transfer_id = 2;
 }
 
 // TODO(pwbug/478): add documentation for details of api contract
-service BundledUpdateService {
+service BundledUpdate {
   // Abort any current software update in progress.
   //
   // Safe to call at any point.
@@ -83,17 +83,31 @@
   // rejected.
   rpc PrepareForUpdate(pw.protobuf.Empty) returns (PrepareUpdateResult) {};
 
-  // Verify the bundle that has been transferred to the staging area. Close the
-  // pw_transfer channel used for staging bundle.
+  // Verifies the bundle that has been transferred to the traging area. Closes
+  // the pw_transfer channel used for the staging bundle. If the verification is
+  // successful it immediately triggers the update of the device, which might
+  // result in a device becoming slow to respond and possibly reboot.
   //
   // Device UpdateState should be READY_FOR_UPDATE when calling, will otherwise
   // be rejected.
+  rpc VerifyAndApplyStagedBundle(pw.protobuf.Empty) returns (OperationResult) {
+  };
+
+  // Verify the bundle that has been transferred to the staging area. Closes the
+  // pw_transfer channel used for the staging bundle.
+  //
+  // Device UpdateState should be READY_FOR_UPDATE when calling, will otherwise
+  // be rejected.
+  //
+  // Note: VerifyAndApplyStagedBundle is preferred if possible to minimize the
+  // duration of time between verification and the apply from a data integrity
+  // and security risk point of view.
   rpc VerifyStagedBundle(pw.protobuf.Empty) returns (OperationResult) {};
 
-  // Trigger the application of the device, which might result in a device
+  // Trigger the update of the device, which might result in a device
   // becoming slow to respond and possibly reboot.
   //
   // Device UpdateState should be VERIFIED_AND_READY_TO_APPLY when calling, will
   // otherwise be rejected.
-  rpc ApplyUpdate(pw.protobuf.Empty) returns (OperationResult) {};
+  rpc ApplyStagedBundle(pw.protobuf.Empty) returns (OperationResult) {};
 }
diff --git a/pw_software_update/bundled_update_service.cc b/pw_software_update/bundled_update_service.cc
new file mode 100644
index 0000000..4435428
--- /dev/null
+++ b/pw_software_update/bundled_update_service.cc
@@ -0,0 +1,116 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_software_update/bundled_update_service.h"
+
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+#include "pw_status/try.h"
+
+namespace pw::software_update {
+
+Status BundledUpdateService::Abort(ServerContext&,
+                                   const pw_protobuf_Empty&,
+                                   pw_software_update_OperationResult&) {
+  return manager_.Abort();
+}
+
+Status BundledUpdateService::SoftwareUpdateState(
+    ServerContext&,
+    const pw_protobuf_Empty&,
+    pw_software_update_OperationResult&) {
+  return Status::Unimplemented();
+}
+
+void BundledUpdateService::GetCurrentManifest(
+    ServerContext&,
+    const pw_protobuf_Empty&,
+    ServerWriter<pw_software_update_Manifest>& writer) {
+  writer.Finish(Status::Unimplemented());
+}
+
+Status BundledUpdateService::VerifyCurrentManifest(
+    ServerContext&,
+    const pw_protobuf_Empty&,
+    pw_software_update_OperationResult&) {
+  return Status::Unimplemented();
+}
+
+Status BundledUpdateService::GetStagedManifest(ServerContext&,
+                                               const pw_protobuf_Empty&,
+                                               pw_software_update_Manifest&) {
+  return Status::Unimplemented();
+}
+
+Status BundledUpdateService::PrepareForUpdate(
+    ServerContext&,
+    [[maybe_unused]] const pw_protobuf_Empty& request,
+    pw_software_update_PrepareUpdateResult& response) {
+  pw_software_update_OperationResult result;
+  result.has_extended_status = false;
+  result.has_state = true;
+  pw_software_update_BundledUpdateState state;
+  state.manager_state =
+      pw_software_update_BundledUpdateState_State_READY_FOR_UPDATE;
+  result.state = state;
+  response.has_result = true;
+  response.result = result;
+  response.has_transfer_id = true;
+  const pw::Result<uint32_t> possible_transfer_id = manager_.GetTransferId();
+  PW_TRY(possible_transfer_id.status());
+  response.transfer_id = possible_transfer_id.value();
+  return manager_.BeforeUpdate();
+}
+
+// TODO: dedupe this with VerifyStagedBundle.
+Status BundledUpdateService::VerifyAndApplyStagedBundle(
+    ServerContext&,
+    const pw_protobuf_Empty&,
+    pw_software_update_OperationResult& response) {
+  // TODO: upstream verification logic
+  response.has_extended_status = false;
+  response.has_state = true;
+  pw_software_update_BundledUpdateState state;
+  state.manager_state =
+      pw_software_update_BundledUpdateState_State_VERIFIED_AND_READY_TO_APPLY;
+  response.state = state;
+  // TODO: defer this handling to the work queue so we can respond here.
+  PW_TRY(manager_.VerifyUpdate());
+  return manager_.ApplyUpdate();
+}
+
+Status BundledUpdateService::VerifyStagedBundle(
+    ServerContext&,
+    const pw_protobuf_Empty&,
+    pw_software_update_OperationResult& response) {
+  // TODO: upstream verification logic
+  response.has_extended_status = false;
+  response.has_state = true;
+  pw_software_update_BundledUpdateState state;
+  state.manager_state =
+      pw_software_update_BundledUpdateState_State_VERIFIED_AND_READY_TO_APPLY;
+  response.state = state;
+  // TODO: defer this handling to the work queue so we can respond here.
+  return manager_.VerifyUpdate();
+}
+
+Status BundledUpdateService::ApplyStagedBundle(
+    ServerContext&,
+    const pw_protobuf_Empty&,
+    pw_software_update_OperationResult&) {
+  // TODO: defer this handling to the work queue so we can respond here.
+  return manager_.ApplyUpdate();
+}
+
+}  // namespace pw::software_update
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 7187385..f7abe0c 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
@@ -14,8 +14,10 @@
 
 #pragma once
 
+#include <string_view>
+
 #include "pw_result/result.h"
-#include "pw_software_update/manifest.h"
+#include "pw_software_update/manifest_accessor.h"
 #include "pw_status/status.h"
 #include "pw_stream/stream.h"
 
@@ -31,7 +33,7 @@
   // (e.g. by checksum, if failed abort partial update and wipe/mark-invalid
   // running manifest)
   virtual Status VerifyTargetFile(
-      [[maybe_unused]] const Manifest& manifest,
+      [[maybe_unused]] const ManifestAccessor& manifest,
       [[maybe_unused]] std::string_view target_file_name) {
     return OkStatus();
   };
@@ -57,7 +59,8 @@
 
   // 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 Manifest& manifest) {
+  virtual Status VerifyMetadata(
+      [[maybe_unused]] const ManifestAccessor& manifest) {
     return OkStatus();
   };
 
diff --git a/pw_software_update/public/pw_software_update/bundled_update_service.h b/pw_software_update/public/pw_software_update/bundled_update_service.h
new file mode 100644
index 0000000..d7af0fe
--- /dev/null
+++ b/pw_software_update/public/pw_software_update/bundled_update_service.h
@@ -0,0 +1,72 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_software_update/bundled_update.rpc.pb.h"
+#include "pw_software_update/update_manager.h"
+#include "pw_status/status.h"
+
+namespace pw::software_update {
+
+// Implementation class for pw_software_update.BundledUpdate.
+class BundledUpdateService
+    : public generated::BundledUpdate<BundledUpdateService> {
+ public:
+  explicit constexpr BundledUpdateService(
+      pw::software_update::BundledUpdateManager& manager)
+      : manager_(manager) {}
+
+  Status Abort(ServerContext&,
+               const pw_protobuf_Empty& request,
+               pw_software_update_OperationResult& response);
+
+  Status SoftwareUpdateState(ServerContext&,
+                             const pw_protobuf_Empty& request,
+                             pw_software_update_OperationResult& response);
+
+  void GetCurrentManifest(ServerContext&,
+                          const pw_protobuf_Empty& request,
+                          ServerWriter<pw_software_update_Manifest>& writer);
+
+  Status VerifyCurrentManifest(ServerContext&,
+                               const pw_protobuf_Empty& request,
+                               pw_software_update_OperationResult& response);
+
+  Status GetStagedManifest(ServerContext&,
+                           const pw_protobuf_Empty& request,
+                           pw_software_update_Manifest& response);
+
+  Status PrepareForUpdate(ServerContext&,
+                          const pw_protobuf_Empty& request,
+                          pw_software_update_PrepareUpdateResult& response);
+
+  Status VerifyAndApplyStagedBundle(
+      ServerContext&,
+      const pw_protobuf_Empty& request,
+      pw_software_update_OperationResult& response);
+
+  Status VerifyStagedBundle(ServerContext&,
+                            const pw_protobuf_Empty& request,
+                            pw_software_update_OperationResult& response);
+
+  Status ApplyStagedBundle(ServerContext&,
+                           const pw_protobuf_Empty& request,
+                           pw_software_update_OperationResult& response);
+
+ private:
+  pw::software_update::BundledUpdateManager& manager_;
+};
+
+}  // namespace pw::software_update
diff --git a/pw_software_update/public/pw_software_update/manifest.h b/pw_software_update/public/pw_software_update/manifest_accessor.h
similarity index 96%
rename from pw_software_update/public/pw_software_update/manifest.h
rename to pw_software_update/public/pw_software_update/manifest_accessor.h
index 3b9cb18..ef7b898 100644
--- a/pw_software_update/public/pw_software_update/manifest.h
+++ b/pw_software_update/public/pw_software_update/manifest_accessor.h
@@ -18,6 +18,6 @@
 
 // TODO(pwbug/456): Place-holder declaration for now. To be implemented
 // and moved elsewhere.
-class Manifest {};
+class ManifestAccessor {};
 
 }  // namespace pw::software_update
diff --git a/pw_software_update/public/pw_software_update/service.h b/pw_software_update/public/pw_software_update/service.h
deleted file mode 100644
index 4caedea..0000000
--- a/pw_software_update/public/pw_software_update/service.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_software_update/service.rpc.pb.h"
diff --git a/pw_software_update/public/pw_software_update/update_bundle.h b/pw_software_update/public/pw_software_update/update_bundle_accessor.h
similarity index 88%
rename from pw_software_update/public/pw_software_update/update_bundle.h
rename to pw_software_update/public/pw_software_update/update_bundle_accessor.h
index b9f85a0..c055852 100644
--- a/pw_software_update/public/pw_software_update/update_bundle.h
+++ b/pw_software_update/public/pw_software_update/update_bundle_accessor.h
@@ -20,12 +20,12 @@
 #include "pw_protobuf/map_utils.h"
 #include "pw_protobuf/message.h"
 #include "pw_software_update/bundled_update_backend.h"
-#include "pw_software_update/manifest.h"
+#include "pw_software_update/manifest_accessor.h"
 #include "pw_stream/memory_stream.h"
 
 namespace pw::software_update {
 
-// UpdateBundle is responsible for parsing, verifying and providing
+// UpdateBundleAccessor is responsible for parsing, verifying and providing
 // target payload access of a software update bundle. It takes the following as
 // inputs:
 //
@@ -39,7 +39,7 @@
 //
 // Exmple of use:
 //
-// UpdateBundle bundle(blob,helper);
+// UpdateBundleAccessor bundle(blob,helper);
 // auto status = bundle.OpenAndVerify(current_manifest);
 // if (!status.ok()) {
 //   // handle error
@@ -69,13 +69,13 @@
 //   // handle error
 //   ...
 // }
-class UpdateBundle {
+class UpdateBundleAccessor {
  public:
-  // UpdateBundle
+  // UpdateBundleAccessor
   // update_bundle - The software update bundle data on storage.
   // backend - project-specific BundledUpdateBackend
-  UpdateBundle(blob_store::BlobStore& update_bundle,
-               BundledUpdateBackend& backend)
+  constexpr UpdateBundleAccessor(blob_store::BlobStore& update_bundle,
+                                 BundledUpdateBackend& backend)
       : bundle_(update_bundle), backend_(backend), bundle_reader_(bundle_) {}
 
   // Opens and verifies the software update bundle, using the TUF process.
@@ -83,7 +83,7 @@
   // Returns:
   // OK - Bundle was successfully opened and verified.
   // TODO(pwbug/456): Add error codes.
-  Status OpenAndVerify(const Manifest& current_manifest);
+  Status OpenAndVerify(const ManifestAccessor& current_manifest);
 
   // Closes the bundle by invalidating the verification and closing
   // the reader to release the read-only lock
@@ -117,6 +117,8 @@
   // TODO(pwbug/456): Figure out a way to propagate error.
   stream::IntervalReader GetTargetPayload(std::string_view target_file);
 
+  protobuf::Message GetDecoder() { return decoder_; }
+
  private:
   blob_store::BlobStore& bundle_;
   BundledUpdateBackend& backend_;
diff --git a/pw_software_update/public/pw_software_update/update_manager.h b/pw_software_update/public/pw_software_update/update_manager.h
new file mode 100644
index 0000000..d45b53b
--- /dev/null
+++ b/pw_software_update/public/pw_software_update/update_manager.h
@@ -0,0 +1,55 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_software_update/bundled_update_backend.h"
+#include "pw_software_update/update_bundle_accessor.h"
+
+namespace pw::software_update {
+
+class BundledUpdateManager {
+ public:
+  constexpr BundledUpdateManager(UpdateBundleAccessor& bundle,
+                                 BundledUpdateBackend& backend)
+
+      : backend_(backend), bundle_(bundle), bundle_open_(false) {}
+
+  pw::Status Abort();
+
+  // Will enable the transfer_id if needed via the BundledUpdateBackend.
+  Result<uint32_t> GetTransferId();
+
+  pw::Status VerifyManifest();
+
+  pw::Status WriteManifest();
+
+  pw::Status ApplyUpdate();
+
+  pw::Status BeforeUpdate();
+
+  pw::Status VerifyUpdate();
+
+ private:
+  BundledUpdateBackend& backend_;
+  UpdateBundleAccessor& bundle_;
+
+  std::optional<uint32_t> transfer_id_;
+  bool bundle_open_;
+
+  // Will disable the transfer_id if needed via the BundledUpdateBackend.
+  void DisableTransferId();
+};
+
+}  // namespace pw::software_update
diff --git a/pw_software_update/tuf.proto b/pw_software_update/tuf.proto
index 4081357..45f4b03 100644
--- a/pw_software_update/tuf.proto
+++ b/pw_software_update/tuf.proto
@@ -17,7 +17,7 @@
 
 syntax = "proto3";
 
-package pw_software_update;
+package pw.software_update;
 
 import "google/protobuf/timestamp.proto";
 
diff --git a/pw_software_update/update_bundle.proto b/pw_software_update/update_bundle.proto
index 856f575..f5f1757 100644
--- a/pw_software_update/update_bundle.proto
+++ b/pw_software_update/update_bundle.proto
@@ -14,7 +14,7 @@
 
 syntax = "proto3";
 
-package pw_software_update;
+package pw.software_update;
 
 import "pw_software_update/tuf.proto";
 
diff --git a/pw_software_update/update_bundle.cc b/pw_software_update/update_bundle_accessor.cc
similarity index 80%
rename from pw_software_update/update_bundle.cc
rename to pw_software_update/update_bundle_accessor.cc
index fd9e894..d989803 100644
--- a/pw_software_update/update_bundle.cc
+++ b/pw_software_update/update_bundle_accessor.cc
@@ -22,8 +22,8 @@
 #include "pw_log/log.h"
 #include "pw_protobuf/message.h"
 #include "pw_result/result.h"
-#include "pw_software_update/update_bundle.h"
 #include "pw_software_update/update_bundle.pwpb.h"
+#include "pw_software_update/update_bundle_accessor.h"
 #include "pw_stream/interval_reader.h"
 #include "pw_stream/memory_stream.h"
 
@@ -34,7 +34,7 @@
 
 }
 
-Status UpdateBundle::OpenAndVerify(const Manifest&) {
+Status UpdateBundleAccessor::OpenAndVerify(const ManifestAccessor&) {
   PW_TRY(bundle_.Init());
   PW_TRY(bundle_reader_.Open());
   decoder_ =
@@ -45,25 +45,25 @@
 }
 
 // Get the target element corresponding to `target_file`
-stream::IntervalReader UpdateBundle::GetTargetPayload(
+stream::IntervalReader UpdateBundleAccessor::GetTargetPayload(
     std::string_view target_file_name) {
   protobuf::StringToBytesMap target_payloads =
       decoder_.AsStringToBytesMap(static_cast<uint32_t>(
-          pw_software_update::UpdateBundle::Fields::TARGET_PAYLOADS));
+          pw::software_update::UpdateBundle::Fields::TARGET_PAYLOADS));
   PW_TRY(target_payloads.status());
   protobuf::Bytes payload = target_payloads[target_file_name];
   PW_TRY(payload.status());
   return payload.GetBytesReader();
 }
 
-Result<bool> UpdateBundle::IsTargetPayloadIncluded(
+Result<bool> UpdateBundleAccessor::IsTargetPayloadIncluded(
     std::string_view target_file_name) {
   // TODO(pwbug/456): Perform personalization check first. If the target
   // is personalized out. Don't need to proceed.
 
   protobuf::StringToMessageMap signed_targets_metadata_map =
       decoder_.AsStringToMessageMap(static_cast<uint32_t>(
-          pw_software_update::UpdateBundle::Fields::TARGETS_METADATA));
+          pw::software_update::UpdateBundle::Fields::TARGETS_METADATA));
   PW_TRY(signed_targets_metadata_map.status());
 
   // There should only be one element in the map, which is the top-level
@@ -73,18 +73,18 @@
   PW_TRY(signed_targets_metadata.status());
 
   protobuf::Message metadata = signed_targets_metadata.AsMessage(
-      static_cast<uint32_t>(pw_software_update::SignedTargetsMetadata::Fields::
+      static_cast<uint32_t>(pw::software_update::SignedTargetsMetadata::Fields::
                                 SERIALIZED_TARGETS_METADATA));
   PW_TRY(metadata.status());
 
   protobuf::RepeatedMessages target_files =
       metadata.AsRepeatedMessages(static_cast<uint32_t>(
-          pw_software_update::TargetsMetadata::Fields::TARGET_FILES));
+          pw::software_update::TargetsMetadata::Fields::TARGET_FILES));
   PW_TRY(target_files.status());
 
   for (protobuf::Message target_file : target_files) {
     protobuf::String name = target_file.AsString(static_cast<uint32_t>(
-        pw_software_update::TargetFile::Fields::FILE_NAME));
+        pw::software_update::TargetFile::Fields::FILE_NAME));
     PW_TRY(name.status());
     Result<bool> file_name_matches = name.Equal(target_file_name);
     PW_TRY(file_name_matches.status());
@@ -96,10 +96,11 @@
   return false;
 }
 
-Status UpdateBundle::WriteManifest(stream::Writer& staged_manifest_writer) {
+Status UpdateBundleAccessor::WriteManifest(
+    stream::Writer& staged_manifest_writer) {
   protobuf::StringToMessageMap signed_targets_metadata_map =
       decoder_.AsStringToMessageMap(static_cast<uint32_t>(
-          pw_software_update::UpdateBundle::Fields::TARGETS_METADATA));
+          pw::software_update::UpdateBundle::Fields::TARGETS_METADATA));
   PW_TRY(signed_targets_metadata_map.status());
 
   // There should only be one element in the map, which is the top-level
@@ -109,7 +110,7 @@
   PW_TRY(signed_targets_metadata.status());
 
   protobuf::Bytes metadata = signed_targets_metadata.AsBytes(
-      static_cast<uint32_t>(pw_software_update::SignedTargetsMetadata::Fields::
+      static_cast<uint32_t>(pw::software_update::SignedTargetsMetadata::Fields::
                                 SERIALIZED_TARGETS_METADATA));
   PW_TRY(metadata.status());
 
@@ -120,7 +121,7 @@
   std::byte stream_pipe_buffer[WRITE_MANIFEST_STREAM_PIPE_BUFFER_SIZE];
   return protobuf::WriteProtoStringToBytesMapEntry(
       static_cast<uint32_t>(
-          pw_software_update::Manifest::Fields::TARGETS_METADATA),
+          pw::software_update::Manifest::Fields::TARGETS_METADATA),
       name_reader,
       kTopLevelTargetsName.size(),
       metadata_reader,
@@ -129,7 +130,7 @@
       staged_manifest_writer);
 }
 
-Status UpdateBundle::Close() {
+Status UpdateBundleAccessor::Close() {
   // TODO(pwbug/456): To be implemented.
   return bundle_reader_.Close();
 }
diff --git a/pw_software_update/update_manager.cc b/pw_software_update/update_manager.cc
new file mode 100644
index 0000000..f5c0860
--- /dev/null
+++ b/pw_software_update/update_manager.cc
@@ -0,0 +1,143 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_software_update/update_manager.h"
+
+#include <string_view>
+
+#include "pw_software_update/manifest_accessor.h"
+#include "pw_software_update/update_bundle.pwpb.h"
+
+namespace pw::software_update {
+
+Status BundledUpdateManager::ApplyUpdate() {
+  PW_LOG_DEBUG("Attempting to apply the update");
+  protobuf::StringToMessageMap signed_targets_metadata_map =
+      bundle_.GetDecoder().AsStringToMessageMap(static_cast<uint32_t>(
+          pw::software_update::UpdateBundle::Fields::TARGETS_METADATA));
+  if (const Status status = signed_targets_metadata_map.status();
+      !status.ok()) {
+    PW_LOG_ERROR("Update bundle does not contain the targets_metadata map: %d",
+                 static_cast<int>(status.code()));
+    return status;
+  }
+
+  // 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()) {
+    PW_LOG_ERROR(
+        "The targets_metadata map does not contain the targets entry: %d",
+        static_cast<int>(status.code()));
+    return status;
+  }
+
+  protobuf::Message targets_metadata = signed_targets_metadata.AsMessage(
+      static_cast<uint32_t>(pw::software_update::SignedTargetsMetadata::Fields::
+                                SERIALIZED_TARGETS_METADATA));
+  if (const Status status = targets_metadata.status(); !status.ok()) {
+    PW_LOG_ERROR(
+        "The targets targets_metadata entry does not contain the "
+        "serialized_target_metadata: %d",
+        static_cast<int>(status.code()));
+    return status;
+  }
+
+  protobuf::RepeatedMessages target_files =
+      targets_metadata.AsRepeatedMessages(static_cast<uint32_t>(
+          pw::software_update::TargetsMetadata::Fields::TARGET_FILES));
+  if (const Status status = target_files.status(); !status.ok()) {
+    PW_LOG_ERROR(
+        "The serialized_target_metadata does not contain target_files: %d",
+        static_cast<int>(status.code()));
+    return status;
+  }
+
+  for (pw::protobuf::Message file_name : target_files) {
+    // TODO: Use a config.h parameter for this.
+    constexpr size_t kFileNameMaxSize = 32;
+    std::array<std::byte, kFileNameMaxSize> buf = {};
+    protobuf::String name = file_name.AsString(static_cast<uint32_t>(
+        pw::software_update::TargetFile::Fields::FILE_NAME));
+    PW_TRY(name.status());
+    const Result<ByteSpan> read_result = name.GetBytesReader().Read(buf);
+    PW_TRY(read_result.status());
+    const ConstByteSpan file_name_span = read_result.value();
+    const std::string_view file_name_view(
+        reinterpret_cast<const char*>(file_name_span.data()),
+        file_name_span.size_bytes());
+    stream::IntervalReader file_reader =
+        bundle_.GetTargetPayload(file_name_view);
+    if (const Status status =
+            backend_.ApplyTargetFile(file_name_view, file_reader);
+        !status.ok()) {
+      PW_LOG_ERROR("Failed to apply target file: %d",
+                   static_cast<int>(status.code()));
+      return status;
+    }
+  }
+
+  return backend_.FinalizeApply();
+}
+
+Result<uint32_t> BundledUpdateManager::GetTransferId() {
+  if (!transfer_id_.has_value()) {
+    Result<uint32_t> possible_transfer_id =
+        backend_.EnableBundleTransferHandler();
+    PW_TRY(possible_transfer_id.status());
+    transfer_id_ = possible_transfer_id.value();
+  }
+  return transfer_id_.value();
+}
+
+pw::Status BundledUpdateManager::VerifyManifest() {
+  return pw::Status::Unimplemented();
+}
+
+pw::Status BundledUpdateManager::WriteManifest() {
+  return pw::Status::Unimplemented();
+}
+
+pw::Status BundledUpdateManager::BeforeUpdate() {
+  return backend_.BeforeUpdateStart();
+}
+
+void BundledUpdateManager::DisableTransferId() {
+  if (!transfer_id_.has_value()) {
+    return;  // Nothing to do, already disabled.
+  }
+  backend_.DisableBundleTransferHandler();
+}
+
+pw::Status BundledUpdateManager::Abort() {
+  DisableTransferId();
+  PW_TRY(backend_.BeforeUpdateAbort());
+  if (bundle_open_) {
+    bundle_.Close();
+  }
+  return OkStatus();
+}
+
+pw::Status BundledUpdateManager::VerifyUpdate() {
+  DisableTransferId();
+  PW_TRY(backend_.BeforeBundleVerify());
+  ManifestAccessor manifest;  // TODO(pwbug/456): Place-holder for now.
+  PW_TRY(bundle_.OpenAndVerify(manifest));
+  bundle_open_ = true;
+  return backend_.AfterBundleVerified();
+}
+
+}  // namespace pw::software_update
diff --git a/pw_software_update/service.cc b/pw_software_update/update_manager_test.cc
similarity index 84%
rename from pw_software_update/service.cc
rename to pw_software_update/update_manager_test.cc
index 8ad1afd..c53e246 100644
--- a/pw_software_update/service.cc
+++ b/pw_software_update/update_manager_test.cc
@@ -12,6 +12,8 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_software_update/service.h"
+#include "pw_software_update/update_manager.h"
 
-namespace pw_software_update {}
\ No newline at end of file
+#include "gtest/gtest.h"
+
+TEST(UpdateManager, Compiles) {}