| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/forensics/feedback_data/metadata.h" |
| |
| #include <lib/zx/clock.h> |
| #include <lib/zx/time.h> |
| #include <zircon/utc.h> |
| |
| #include <optional> |
| #include <set> |
| |
| #include "src/developer/forensics/feedback_data/constants.h" |
| #include "src/developer/forensics/feedback_data/errors.h" |
| #include "src/developer/forensics/utils/cobalt/metrics.h" |
| #include "src/developer/forensics/utils/errors.h" |
| #include "src/lib/fxl/strings/split_string.h" |
| #include "third_party/rapidjson/include/rapidjson/document.h" |
| #include "third_party/rapidjson/include/rapidjson/prettywriter.h" |
| #include "third_party/rapidjson/include/rapidjson/rapidjson.h" |
| #include "third_party/rapidjson/include/rapidjson/stringbuffer.h" |
| |
| namespace forensics { |
| namespace feedback_data { |
| namespace { |
| |
| using namespace rapidjson; |
| |
| std::set<std::string> kUtcMonotonicDifferenceAllowlist = { |
| kAttachmentInspect, |
| kAttachmentLogKernel, |
| kAttachmentLogSystem, |
| }; |
| |
| std::set<std::string> kPreviousBootUtcMonotonicDifferenceAllowlist = { |
| kAttachmentLogSystemPrevious, |
| }; |
| |
| std::string ToString(const enum feedback::AttachmentValue::State state) { |
| switch (state) { |
| case feedback::AttachmentValue::State::kComplete: |
| return "complete"; |
| case feedback::AttachmentValue::State::kPartial: |
| return "partial"; |
| case feedback::AttachmentValue::State::kMissing: |
| return "missing"; |
| } |
| } |
| |
| // Create a complete list set of annotations from the collected annotations and the allowlist. |
| feedback::Annotations AllAnnotations(const std::set<std::string>& allowlist, |
| const feedback::Annotations& annotations) { |
| feedback::Annotations all_annotations = annotations; |
| |
| for (const auto& key : allowlist) { |
| if (all_annotations.find(key) == all_annotations.end()) { |
| // There is an annotation in the allowlist that was not produced by any provider. This |
| // indicates a logical error on the Feedback-side. |
| all_annotations.insert({key, Error::kLogicError}); |
| } |
| } |
| |
| return all_annotations; |
| } |
| |
| // Create a complete list set of attachments from the collected attachments and the allowlist. |
| feedback::Attachments AllAttachments(const feedback::AttachmentKeys& allowlist, |
| const feedback::Attachments& attachments) { |
| feedback::Attachments all_attachments; |
| |
| // Because attachments can contain large blobs of text and we only care about the state of the |
| // attachment and its associated error, we don't copy the value of the attachment. |
| for (const auto& [k, v] : attachments) { |
| switch (v.State()) { |
| case feedback::AttachmentValue::State::kComplete: |
| all_attachments.insert({k, feedback::AttachmentValue("")}); |
| break; |
| case feedback::AttachmentValue::State::kPartial: |
| all_attachments.insert({k, feedback::AttachmentValue("", v.Error())}); |
| break; |
| case feedback::AttachmentValue::State::kMissing: |
| all_attachments.insert({k, v}); |
| break; |
| } |
| } |
| |
| for (const auto& key : allowlist) { |
| if (all_attachments.find(key) == all_attachments.end()) { |
| all_attachments.insert({key, feedback::AttachmentValue(Error::kLogicError)}); |
| } |
| } |
| |
| return all_attachments; |
| } |
| |
| void AddUtcMonotonicDifference(const std::optional<zx::duration>& utc_monotonic_difference, |
| Value* file, Document::AllocatorType& allocator) { |
| if (!utc_monotonic_difference.has_value() || !file->IsObject() || |
| file->HasMember("utc_monotonic_difference_nanos") || |
| (file->HasMember("state") && (*file)["state"].IsString() && |
| (*file)["state"].GetString() == ToString(feedback::AttachmentValue::State::kMissing))) { |
| return; |
| } |
| |
| file->AddMember("utc_monotonic_difference_nanos", |
| Value().SetInt64(utc_monotonic_difference.value().get()), allocator); |
| } |
| |
| void AddUtcMonotonicDifferences( |
| const std::optional<zx::duration> utc_monotonic_difference, |
| const std::optional<zx::duration> previous_boot_utc_monotonic_difference, |
| Document* metadata_json) { |
| if (!metadata_json->HasMember("files")) { |
| return; |
| } |
| |
| for (auto& file : (*metadata_json)["files"].GetObject()) { |
| if (kUtcMonotonicDifferenceAllowlist.find(file.name.GetString()) != |
| kUtcMonotonicDifferenceAllowlist.end()) { |
| AddUtcMonotonicDifference(utc_monotonic_difference, &file.value, |
| metadata_json->GetAllocator()); |
| } |
| |
| if (kPreviousBootUtcMonotonicDifferenceAllowlist.find(file.name.GetString()) != |
| kPreviousBootUtcMonotonicDifferenceAllowlist.end()) { |
| AddUtcMonotonicDifference(previous_boot_utc_monotonic_difference, &file.value, |
| metadata_json->GetAllocator()); |
| } |
| } |
| } |
| |
| void AddAttachments(const feedback::AttachmentKeys& attachment_allowlist, |
| const feedback::Attachments& attachments, Document* metadata_json) { |
| if (attachment_allowlist.empty()) { |
| return; |
| } |
| |
| auto& allocator = metadata_json->GetAllocator(); |
| auto MakeValue = [&allocator](const std::string& v) { return Value(v, allocator); }; |
| |
| for (const auto& [name, v] : AllAttachments(attachment_allowlist, attachments)) { |
| Value file(kObjectType); |
| |
| file.AddMember("state", MakeValue(ToString(v.State())), allocator); |
| if (v.HasError()) { |
| file.AddMember("error", MakeValue(ToReason(v.Error())), allocator); |
| } |
| |
| (*metadata_json)["files"].AddMember(MakeValue(name), file, allocator); |
| } |
| } |
| |
| void AddAnnotationsJson(const std::set<std::string>& annotation_allowlist, |
| const feedback::Annotations& annotations, |
| const bool missing_non_platform_annotations, Document* metadata_json) { |
| const feedback::Annotations all_annotations = AllAnnotations(annotation_allowlist, annotations); |
| |
| bool has_non_platform = all_annotations.size() > annotation_allowlist.size(); |
| if (annotation_allowlist.empty() && !(has_non_platform || missing_non_platform_annotations)) { |
| return; |
| } |
| |
| auto& allocator = metadata_json->GetAllocator(); |
| auto MakeValue = [&allocator](const std::string& v) { return Value(v, allocator); }; |
| |
| Value present(kArrayType); |
| Value missing(kObjectType); |
| |
| size_t num_present_platform = 0u; |
| size_t num_missing_platform = 0u; |
| for (const auto& [k, v] : all_annotations) { |
| if (annotation_allowlist.find(k) == annotation_allowlist.end()) { |
| continue; |
| } |
| |
| Value key(MakeValue(k)); |
| if (v.HasValue()) { |
| present.PushBack(key, allocator); |
| ++num_present_platform; |
| } else { |
| missing.AddMember(key, MakeValue(ToReason(v.Error())), allocator); |
| ++num_missing_platform; |
| } |
| } |
| |
| if (has_non_platform || missing_non_platform_annotations) { |
| if (!missing_non_platform_annotations) { |
| present.PushBack("non-platform annotations", allocator); |
| } else { |
| missing.AddMember("non-platform annotations", "too many non-platfrom annotations added", |
| allocator); |
| } |
| } |
| |
| Value state; |
| if (num_present_platform == annotation_allowlist.size() && !missing_non_platform_annotations) { |
| state = "complete"; |
| } else if (num_missing_platform == annotation_allowlist.size() && !has_non_platform && |
| missing_non_platform_annotations) { |
| state = "missing"; |
| } else { |
| state = "partial"; |
| } |
| |
| Value annotations_json(kObjectType); |
| annotations_json.AddMember("state", state, allocator); |
| annotations_json.AddMember("missing annotations", missing, allocator); |
| annotations_json.AddMember("present annotations", present, allocator); |
| |
| (*metadata_json)["files"].AddMember("annotations.json", annotations_json, allocator); |
| } |
| |
| void AddLogRedactionCanary(const std::string& log_redaction_canary, Document* metadata_json) { |
| auto& allocator = metadata_json->GetAllocator(); |
| Value lines(kArrayType); |
| for (const std::string& line : |
| fxl::SplitStringCopy(log_redaction_canary, "\n", fxl::WhiteSpaceHandling::kTrimWhitespace, |
| fxl::SplitResult::kSplitWantNonEmpty)) { |
| lines.PushBack(Value(line, allocator), allocator); |
| } |
| |
| metadata_json->AddMember("log_redaction_canary", lines, allocator); |
| } |
| |
| } // namespace |
| |
| Metadata::Metadata(async_dispatcher_t* dispatcher, timekeeper::Clock* clock, RedactorBase* redactor, |
| const bool is_first_instance, const std::set<std::string>& annotation_allowlist, |
| const feedback::AttachmentKeys& attachment_allowlist) |
| : log_redaction_canary_(redactor->UnredactedCanary()), |
| annotation_allowlist_(annotation_allowlist), |
| attachment_allowlist_(attachment_allowlist), |
| utc_provider_(dispatcher, zx::unowned_clock(zx_utc_reference_get()), clock, |
| PreviousBootFile::FromCache(is_first_instance, kUtcMonotonicDifferenceFile)) { |
| redactor->Redact(log_redaction_canary_); |
| } |
| |
| std::string Metadata::MakeMetadata(const feedback::Annotations& annotations, |
| const feedback::Attachments& attachments, |
| const std::string& snapshot_uuid, |
| bool missing_non_platform_annotations) { |
| Document metadata_json(kObjectType); |
| auto& allocator = metadata_json.GetAllocator(); |
| |
| auto MetadataString = [&metadata_json]() { |
| StringBuffer buffer; |
| PrettyWriter<StringBuffer> writer(buffer); |
| |
| metadata_json.Accept(writer); |
| |
| return std::string(buffer.GetString()); |
| }; |
| |
| // Insert all top-level fields |
| metadata_json.AddMember("snapshot_version", Value(SnapshotVersion::kString, allocator), |
| allocator); |
| metadata_json.AddMember("metadata_version", Value(Metadata::kVersion, allocator), allocator); |
| metadata_json.AddMember("snapshot_uuid", Value(snapshot_uuid, allocator), allocator); |
| metadata_json.AddMember("files", Value(kObjectType), allocator); |
| AddLogRedactionCanary(log_redaction_canary_, &metadata_json); |
| |
| const bool has_non_platform_annotations = annotations.size() > annotation_allowlist_.size(); |
| |
| if (annotation_allowlist_.empty() && attachment_allowlist_.empty() && |
| !has_non_platform_annotations && !missing_non_platform_annotations) { |
| return MetadataString(); |
| } |
| |
| AddAttachments(attachment_allowlist_, attachments, &metadata_json); |
| AddAnnotationsJson(annotation_allowlist_, annotations, missing_non_platform_annotations, |
| &metadata_json); |
| AddUtcMonotonicDifferences(utc_provider_.CurrentUtcMonotonicDifference(), |
| utc_provider_.PreviousBootUtcMonotonicDifference(), &metadata_json); |
| |
| return MetadataString(); |
| } |
| |
| } // namespace feedback_data |
| } // namespace forensics |