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