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