xref: /aosp_15_r20/external/pigweed/pw_software_update/bundled_update_service_pwpb.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #define PW_LOG_MODULE_NAME "PWSU"
16 #define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
17 
18 #include "pw_software_update/bundled_update_service_pwpb.h"
19 
20 #include <mutex>
21 #include <string_view>
22 
23 #include "pw_log/log.h"
24 #include "pw_result/result.h"
25 #include "pw_software_update/config.h"
26 #include "pw_software_update/manifest_accessor.h"
27 #include "pw_software_update/update_bundle.pwpb.h"
28 #include "pw_status/status.h"
29 #include "pw_status/status_with_size.h"
30 #include "pw_status/try.h"
31 #include "pw_string/string_builder.h"
32 #include "pw_string/util.h"
33 #include "pw_sync/borrow.h"
34 #include "pw_sync/mutex.h"
35 #include "pw_tokenizer/tokenize.h"
36 
37 namespace pw::software_update {
38 namespace {
39 using BorrowedStatus =
40     sync::BorrowedPointer<BundledUpdateStatus::Message, sync::Mutex>;
41 
42 // TODO(keir): Convert all the CHECKs in the RPC service to gracefully report
43 // errors.
44 #define SET_ERROR(res, message, ...)                                     \
45   do {                                                                   \
46     PW_LOG_ERROR(message, __VA_ARGS__);                                  \
47     if (!IsFinished()) {                                                 \
48       Finish(res);                                                       \
49       {                                                                  \
50         BorrowedStatus borrowed_status = status_.acquire();              \
51         size_t note_size = borrowed_status->note.max_size();             \
52         borrowed_status->note.resize(note_size);                         \
53         PW_TOKENIZE_TO_BUFFER(                                           \
54             &borrowed_status->note, &(note_size), message, __VA_ARGS__); \
55         borrowed_status->note.resize(note_size);                         \
56       }                                                                  \
57     }                                                                    \
58   } while (false)
59 }  // namespace
60 
GetStatus(const pw::protobuf::Empty::Message &,BundledUpdateStatus::Message & response)61 Status BundledUpdateService::GetStatus(const pw::protobuf::Empty::Message&,
62                                        BundledUpdateStatus::Message& response) {
63   response = *status_.acquire();
64   return OkStatus();
65 }
66 
Start(const StartRequest::Message & request,BundledUpdateStatus::Message & response)67 Status BundledUpdateService::Start(const StartRequest::Message& request,
68                                    BundledUpdateStatus::Message& response) {
69   std::lock_guard lock(mutex_);
70   // Check preconditions.
71   const BundledUpdateState::Enum state = status_.acquire()->state;
72   if (state != BundledUpdateState::Enum::kInactive) {
73     SET_ERROR(BundledUpdateResult::Enum::kUnknownError,
74               "Start() can only be called from INACTIVE state. "
75               "Current state: %d. Abort() then Reset() must be called first",
76               static_cast<int>(state));
77     response = *status_.acquire();
78     return Status::FailedPrecondition();
79   }
80 
81   {
82     BorrowedStatus borrowed_status = status_.acquire();
83     PW_DCHECK(!borrowed_status->transfer_id.has_value());
84     PW_DCHECK(!borrowed_status->result.has_value());
85     PW_DCHECK(
86         !borrowed_status->current_state_progress_hundreth_percent.has_value());
87     PW_DCHECK(borrowed_status->bundle_filename.empty());
88     PW_DCHECK(borrowed_status->note.empty());
89   }
90 
91   // Notify the backend of pending transfer.
92   if (const Status status = backend_.BeforeUpdateStart(); !status.ok()) {
93     SET_ERROR(BundledUpdateResult::Enum::kUnknownError,
94               "Backend error on BeforeUpdateStart()");
95     response = *status_.acquire();
96     return status;
97   }
98 
99   // Enable bundle transfer.
100   Result<uint32_t> possible_transfer_id =
101       backend_.EnableBundleTransferHandler(request.bundle_filename);
102   if (!possible_transfer_id.ok()) {
103     SET_ERROR(BundledUpdateResult::Enum::kTransferFailed,
104               "Couldn't enable bundle transfer");
105     response = *status_.acquire();
106     return possible_transfer_id.status();
107   }
108 
109   // Update state.
110   {
111     BorrowedStatus borrowed_status = status_.acquire();
112     borrowed_status->transfer_id = possible_transfer_id.value();
113     if (!request.bundle_filename.empty()) {
114       borrowed_status->bundle_filename = request.bundle_filename;
115     }
116     borrowed_status->state = BundledUpdateState::Enum::kTransferring;
117     response = *borrowed_status;
118   }
119   return OkStatus();
120 }
121 
SetTransferred(const pw::protobuf::Empty::Message &,BundledUpdateStatus::Message & response)122 Status BundledUpdateService::SetTransferred(
123     const pw::protobuf::Empty::Message&,
124     BundledUpdateStatus::Message& response) {
125   const BundledUpdateState::Enum state = status_.acquire()->state;
126 
127   if (state != BundledUpdateState::Enum::kTransferring &&
128       state != BundledUpdateState::Enum::kInactive) {
129     std::lock_guard lock(mutex_);
130     SET_ERROR(BundledUpdateResult::Enum::kUnknownError,
131               "SetTransferred() can only be called from TRANSFERRING or "
132               "INACTIVE state. State: %d",
133               static_cast<int>(state));
134     response = *status_.acquire();
135     return OkStatus();
136   }
137 
138   NotifyTransferSucceeded();
139 
140   response = *status_.acquire();
141   return OkStatus();
142 }
143 
144 // TODO(elipsitz): Check for "ABORTING" state and bail if it's set.
DoVerify()145 void BundledUpdateService::DoVerify() {
146   std::lock_guard guard(mutex_);
147   const BundledUpdateState::Enum state = status_.acquire()->state;
148 
149   if (state == BundledUpdateState::Enum::kVerified) {
150     return;  // Already done!
151   }
152 
153   // Ensure we're in the right state.
154   if (state != BundledUpdateState::Enum::kTransferred) {
155     SET_ERROR(BundledUpdateResult::Enum::kVerifyFailed,
156               "DoVerify() must be called from TRANSFERRED state. State: %d",
157               static_cast<int>(state));
158     return;
159   }
160 
161   status_.acquire()->state = BundledUpdateState::Enum::kVerifying;
162 
163   // Notify backend about pending verify.
164   if (const Status status = backend_.BeforeBundleVerify(); !status.ok()) {
165     SET_ERROR(BundledUpdateResult::Enum::kVerifyFailed,
166               "Backend::BeforeBundleVerify() failed");
167     return;
168   }
169 
170   // Do the actual verify.
171   Status status = bundle_.OpenAndVerify();
172   if (!status.ok()) {
173     SET_ERROR(BundledUpdateResult::Enum::kVerifyFailed,
174               "Bundle::OpenAndVerify() failed");
175     return;
176   }
177   bundle_open_ = true;
178 
179   // Have the backend verify the user_manifest if present.
180   if (!backend_.VerifyManifest(bundle_.GetManifest()).ok()) {
181     SET_ERROR(BundledUpdateResult::Enum::kVerifyFailed,
182               "Backend::VerifyUserManifest() failed");
183     return;
184   }
185 
186   // Notify backend we're done verifying.
187   status = backend_.AfterBundleVerified();
188   if (!status.ok()) {
189     SET_ERROR(BundledUpdateResult::Enum::kVerifyFailed,
190               "Backend::AfterBundleVerified() failed");
191     return;
192   }
193   status_.acquire()->state = BundledUpdateState::Enum::kVerified;
194 }
195 
Verify(const pw::protobuf::Empty::Message &,BundledUpdateStatus::Message & response)196 Status BundledUpdateService::Verify(const pw::protobuf::Empty::Message&,
197                                     BundledUpdateStatus::Message& response) {
198   std::lock_guard lock(mutex_);
199   const BundledUpdateState::Enum state = status_.acquire()->state;
200 
201   // Already done? Bail.
202   if (state == BundledUpdateState::Enum::kVerified) {
203     PW_LOG_DEBUG("Skipping verify since already verified");
204     return OkStatus();
205   }
206 
207   // TODO(elipsitz): Remove the transferring permitted state here ASAP.
208   // Ensure we're in the right state.
209   if ((state != BundledUpdateState::Enum::kTransferring) &&
210       (state != BundledUpdateState::Enum::kTransferred)) {
211     SET_ERROR(BundledUpdateResult::Enum::kVerifyFailed,
212               "Verify() must be called from TRANSFERRED state. State: %d",
213               static_cast<int>(state));
214     response = *status_.acquire();
215     return Status::FailedPrecondition();
216   }
217 
218   // TODO(elipsitz): We should probably make this mode idempotent.
219   // Already doing what was asked? Bail.
220   if (work_enqueued_) {
221     PW_LOG_DEBUG("Verification is already active");
222     return OkStatus();
223   }
224 
225   // The backend's ApplyReboot as part of DoApply() shall be configured
226   // such that this RPC can send out the reply before the device reboots.
227   const Status status = work_queue_.PushWork([this] {
228     {
229       std::lock_guard y_lock(this->mutex_);
230       PW_DCHECK(this->work_enqueued_);
231     }
232     this->DoVerify();
233     {
234       std::lock_guard y_lock(this->mutex_);
235       this->work_enqueued_ = false;
236     }
237   });
238   if (!status.ok()) {
239     SET_ERROR(BundledUpdateResult::Enum::kVerifyFailed,
240               "Unable to equeue apply to work queue");
241     response = *status_.acquire();
242     return status;
243   }
244   work_enqueued_ = true;
245 
246   response = *status_.acquire();
247   return OkStatus();
248 }
249 
Apply(const pw::protobuf::Empty::Message &,BundledUpdateStatus::Message & response)250 Status BundledUpdateService::Apply(const pw::protobuf::Empty::Message&,
251                                    BundledUpdateStatus::Message& response) {
252   std::lock_guard lock(mutex_);
253   const BundledUpdateState::Enum state = status_.acquire()->state;
254 
255   // We do not wait to go into a finished error state if we're already
256   // applying, instead just let them know that yes we are working on it --
257   // hold on.
258   if (state == BundledUpdateState::Enum::kApplying) {
259     PW_LOG_DEBUG("Apply is already active");
260     return OkStatus();
261   }
262 
263   if ((state != BundledUpdateState::Enum::kTransferred) &&
264       (state != BundledUpdateState::Enum::kVerified)) {
265     SET_ERROR(BundledUpdateResult::Enum::kApplyFailed,
266               "Apply() must be called from TRANSFERRED or VERIFIED state. "
267               "State: %d",
268               static_cast<int>(state));
269     return Status::FailedPrecondition();
270   }
271 
272   // TODO(elipsitz): We should probably make these all idempotent properly.
273   if (work_enqueued_) {
274     PW_LOG_DEBUG("Apply is already active");
275     return OkStatus();
276   }
277 
278   // The backend's ApplyReboot as part of DoApply() shall be configured
279   // such that this RPC can send out the reply before the device reboots.
280   const Status status = work_queue_.PushWork([this] {
281     {
282       std::lock_guard y_lock(this->mutex_);
283       PW_DCHECK(this->work_enqueued_);
284     }
285     // Error reporting is handled in DoVerify and DoApply.
286     this->DoVerify();
287     this->DoApply();
288     {
289       std::lock_guard y_lock(this->mutex_);
290       this->work_enqueued_ = false;
291     }
292   });
293   if (!status.ok()) {
294     SET_ERROR(BundledUpdateResult::Enum::kApplyFailed,
295               "Unable to equeue apply to work queue");
296     response = *status_.acquire();
297     return status;
298   }
299   work_enqueued_ = true;
300 
301   return OkStatus();
302 }
303 
DoApply()304 void BundledUpdateService::DoApply() {
305   std::lock_guard guard(mutex_);
306   const BundledUpdateState::Enum state = status_.acquire()->state;
307 
308   PW_LOG_DEBUG("Attempting to apply the update");
309   if (state != BundledUpdateState::Enum::kVerified) {
310     SET_ERROR(BundledUpdateResult::Enum::kApplyFailed,
311               "Apply() must be called from VERIFIED state. State: %d",
312               static_cast<int>(state));
313     return;
314   }
315 
316   status_.acquire()->state = BundledUpdateState::Enum::kApplying;
317 
318   if (const Status status = backend_.BeforeApply(); !status.ok()) {
319     SET_ERROR(BundledUpdateResult::Enum::kApplyFailed,
320               "BeforeApply() returned unsuccessful result: %d",
321               static_cast<int>(status.code()));
322     return;
323   }
324 
325   // In order to report apply progress, quickly scan to see how many bytes
326   // will be applied.
327   Result<uint64_t> total_payload_bytes = bundle_.GetTotalPayloadSize();
328   PW_CHECK_OK(total_payload_bytes.status());
329   size_t target_file_bytes_to_apply =
330       static_cast<size_t>(total_payload_bytes.value());
331 
332   protobuf::RepeatedMessages target_files =
333       bundle_.GetManifest().GetTargetFiles();
334   PW_CHECK_OK(target_files.status());
335 
336   size_t target_file_bytes_applied = 0;
337   for (pw::protobuf::Message file_name : target_files) {
338     std::array<std::byte, MAX_TARGET_NAME_LENGTH> buf = {};
339     protobuf::String name = file_name.AsString(static_cast<uint32_t>(
340         pw::software_update::TargetFile::Fields::kFileName));
341     PW_CHECK_OK(name.status());
342     const Result<ByteSpan> read_result = name.GetBytesReader().Read(buf);
343     PW_CHECK_OK(read_result.status());
344     const ConstByteSpan file_name_span = read_result.value();
345     const std::string_view file_name_view(
346         reinterpret_cast<const char*>(file_name_span.data()),
347         file_name_span.size_bytes());
348     if (file_name_view.compare(kUserManifestTargetFileName) == 0) {
349       continue;  // user_manifest is not applied by the backend.
350     }
351     // Try to get an IntervalReader for the current file.
352     stream::IntervalReader file_reader =
353         bundle_.GetTargetPayload(file_name_view);
354     if (file_reader.status().IsNotFound()) {
355       PW_LOG_INFO(
356           "Contents of file %s missing from bundle; ignoring",
357           pw::MakeString<MAX_TARGET_NAME_LENGTH>(file_name_view).c_str());
358       continue;
359     }
360     if (!file_reader.ok()) {
361       SET_ERROR(BundledUpdateResult::Enum::kApplyFailed,
362                 "Could not open contents of file %s from bundle; "
363                 "aborting update apply phase",
364                 MakeString<MAX_TARGET_NAME_LENGTH>(file_name_view).c_str());
365       return;
366     }
367 
368     const size_t bundle_offset = file_reader.start();
369     if (const Status status = backend_.ApplyTargetFile(
370             file_name_view, file_reader, bundle_offset);
371         !status.ok()) {
372       SET_ERROR(BundledUpdateResult::Enum::kApplyFailed,
373                 "Failed to apply target file: %d",
374                 static_cast<int>(status.code()));
375       return;
376     }
377     target_file_bytes_applied += file_reader.interval_size();
378     const uint32_t progress_hundreth_percent =
379         (static_cast<uint64_t>(target_file_bytes_applied) * 100 * 100) /
380         target_file_bytes_to_apply;
381     PW_LOG_DEBUG("Apply progress: %zu/%zu Bytes (%ld%%)",
382                  target_file_bytes_applied,
383                  target_file_bytes_to_apply,
384                  static_cast<unsigned long>(progress_hundreth_percent / 100));
385     {
386       BorrowedStatus borrowed_status = status_.acquire();
387       borrowed_status->current_state_progress_hundreth_percent =
388           progress_hundreth_percent;
389     }
390   }
391 
392   // TODO(davidrogers): Add new APPLY_REBOOTING to distinguish between pre and
393   // post reboot.
394 
395   // Finalize the apply.
396   if (const Status status = backend_.ApplyReboot(); !status.ok()) {
397     SET_ERROR(BundledUpdateResult::Enum::kApplyFailed,
398               "Failed to do the apply reboot: %d",
399               static_cast<int>(status.code()));
400     return;
401   }
402 
403   // TODO(davidrogers): Move this to MaybeFinishApply() once available.
404   Finish(BundledUpdateResult::Enum::kSuccess);
405 }
406 
Abort(const pw::protobuf::Empty::Message &,BundledUpdateStatus::Message & response)407 Status BundledUpdateService::Abort(const pw::protobuf::Empty::Message&,
408                                    BundledUpdateStatus::Message& response) {
409   std::lock_guard lock(mutex_);
410   const BundledUpdateState::Enum state = status_.acquire()->state;
411 
412   if (state == BundledUpdateState::Enum::kApplying) {
413     return Status::FailedPrecondition();
414   }
415 
416   if (state == BundledUpdateState::Enum::kInactive ||
417       state == BundledUpdateState::Enum::kFinished) {
418     SET_ERROR(BundledUpdateResult::Enum::kUnknownError,
419               "Tried to abort when already INACTIVE or FINISHED");
420     return Status::FailedPrecondition();
421   }
422   // TODO(elipsitz): Switch abort to async; this state change isn't externally
423   // visible.
424   status_.acquire()->state = BundledUpdateState::Enum::kAborting;
425 
426   SET_ERROR(BundledUpdateResult::Enum::kAborted, "Update abort requested");
427   response = *status_.acquire();
428   return OkStatus();
429 }
430 
Reset(const pw::protobuf::Empty::Message &,BundledUpdateStatus::Message & response)431 Status BundledUpdateService::Reset(const pw::protobuf::Empty::Message&,
432                                    BundledUpdateStatus::Message& response) {
433   std::lock_guard lock(mutex_);
434   const BundledUpdateState::Enum state = status_.acquire()->state;
435 
436   if (state == BundledUpdateState::Enum::kInactive) {
437     return OkStatus();  // Already done.
438   }
439 
440   if (state != BundledUpdateState::Enum::kFinished) {
441     SET_ERROR(
442         BundledUpdateResult::Enum::kUnknownError,
443         "Reset() must be called from FINISHED or INACTIVE state. State: %d",
444         static_cast<int>(state));
445     response = *status_.acquire();
446     return Status::FailedPrecondition();
447   }
448 
449   {
450     BorrowedStatus status = status_.acquire();
451     *status = {};  // Force-init all fields to zero.
452     status->state = BundledUpdateState::Enum::kInactive;
453   }
454 
455   // Reset the bundle.
456   if (bundle_open_) {
457     // TODO(elipsitz): Revisit whether this is recoverable; maybe eliminate
458     // CHECK.
459     PW_CHECK_OK(bundle_.Close());
460     bundle_open_ = false;
461   }
462 
463   response = *status_.acquire();
464   return OkStatus();
465 }
466 
NotifyTransferSucceeded()467 void BundledUpdateService::NotifyTransferSucceeded() {
468   std::lock_guard lock(mutex_);
469   const BundledUpdateState::Enum state = status_.acquire()->state;
470 
471   if (state != BundledUpdateState::Enum::kTransferring) {
472     // This can happen if the update gets Abort()'d during the transfer and
473     // the transfer completes successfully.
474     PW_LOG_WARN(
475         "Got transfer succeeded notification when not in TRANSFERRING state. "
476         "State: %d",
477         static_cast<int>(state));
478   }
479 
480   const bool transfer_ongoing = status_.acquire()->transfer_id.has_value();
481   if (transfer_ongoing) {
482     backend_.DisableBundleTransferHandler();
483     status_.acquire()->transfer_id.reset();
484   } else {
485     PW_LOG_WARN("No ongoing transfer found, forcefully set TRANSFERRED.");
486   }
487 
488   status_.acquire()->state = BundledUpdateState::Enum::kTransferred;
489 }
490 
Finish(BundledUpdateResult::Enum result)491 void BundledUpdateService::Finish(BundledUpdateResult::Enum result) {
492   if (result == BundledUpdateResult::Enum::kSuccess) {
493     BorrowedStatus borrowed_status = status_.acquire();
494     borrowed_status->current_state_progress_hundreth_percent.reset();
495   } else {
496     // In the case of error, notify backend that we're about to abort the
497     // software update.
498     PW_CHECK_OK(backend_.BeforeUpdateAbort());
499   }
500 
501   // Turn down the transfer if one is in progress.
502   const bool transfer_ongoing = status_.acquire()->transfer_id.has_value();
503   if (transfer_ongoing) {
504     backend_.DisableBundleTransferHandler();
505   }
506   status_.acquire()->transfer_id.reset();
507 
508   // Close out any open bundles.
509   if (bundle_open_) {
510     // TODO(elipsitz): Revisit this check; may be able to recover.
511     PW_CHECK_OK(bundle_.Close());
512     bundle_open_ = false;
513   }
514   {
515     BorrowedStatus borrowed_status = status_.acquire();
516     borrowed_status->state = BundledUpdateState::Enum::kFinished;
517     borrowed_status->result = result;
518   }
519 }
520 
521 }  // namespace pw::software_update
522