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