| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "rmad/logs/logs_utils.h" |
| |
| #include <functional> |
| #include <map> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/json/json_string_value_serializer.h> |
| #include <base/memory/scoped_refptr.h> |
| #include <base/strings/strcat.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/strings/string_util.h> |
| #include <base/time/time.h> |
| #include <base/values.h> |
| |
| #include "rmad/constants.h" |
| #include "rmad/logs/logs_constants.h" |
| #include "rmad/proto_bindings/rmad.pb.h" |
| #include "rmad/utils/json_store.h" |
| #include "rmad/utils/type_conversions.h" |
| |
| namespace rmad { |
| |
| namespace { |
| |
| const char* GetStateName(RmadState::StateCase state) { |
| auto it = kStateNames.find(state); |
| CHECK(it != kStateNames.end()); |
| return it->second.data(); |
| } |
| |
| std::string JoinValueList( |
| const base::Value::List* list, |
| const std::function<std::string(const base::Value&)>& function, |
| const std::string& separator) { |
| auto begin = list->begin(); |
| auto end = list->end(); |
| std::ostringstream list_stream; |
| if (begin != end) { |
| list_stream << function(*begin); |
| while (++begin != end) { |
| list_stream << separator << function(*begin); |
| } |
| } |
| return list_stream.str(); |
| } |
| |
| bool AddEventToJson(scoped_refptr<JsonStore> json_store, |
| RmadState::StateCase state, |
| LogEventType event_type, |
| base::Value::Dict&& details) { |
| base::Value::Dict event; |
| event.Set(kTimestamp, base::Time::Now().InSecondsFSinceUnixEpoch()); |
| event.Set(kStateId, static_cast<int>(state)); |
| event.Set(kType, static_cast<int>(event_type)); |
| event.Set(kDetails, std::move(details)); |
| |
| base::Value logs(base::Value::Type::DICT); |
| if (json_store->GetValue(kLogs, &logs)) { |
| CHECK(logs.is_dict()); |
| } |
| |
| // EnsureList() returns a pointer to the `events` JSON so no need to add it |
| // back to `logs`. |
| base::Value::List* events = logs.GetDict().EnsureList(kEvents); |
| events->Append(std::move(event)); |
| |
| return json_store->SetValue(kLogs, std::move(logs)); |
| } |
| |
| std::string GetCalibrationStatusString(const base::Value::Dict& component) { |
| const std::string component_name = *component.FindString(kLogComponent); |
| const LogCalibrationStatus status = static_cast<LogCalibrationStatus>( |
| component.FindInt(kLogCalibrationStatus).value()); |
| auto it = kLogCalibrationStatusMap.find(status); |
| CHECK(it != kLogCalibrationStatusMap.end()); |
| return base::StrCat({component_name, " - ", it->second}); |
| } |
| |
| std::string GenerateTextLogString(scoped_refptr<JsonStore> json_store) { |
| std::string generated_text_log; |
| |
| base::Value logs(base::Value::Type::DICT); |
| json_store->GetValue(kLogs, &logs); |
| const base::Value::List* events = logs.GetDict().FindList(kEvents); |
| |
| for (const base::Value& event : *events) { |
| const base::Value::Dict& event_dict = event.GetDict(); |
| const int type = event_dict.FindInt(kType).value(); |
| const int current_state_id = event_dict.FindInt(kStateId).value(); |
| |
| // Append the timestamp prefix. |
| base::Time::Exploded exploded; |
| base::Time::FromSecondsSinceUnixEpoch( |
| event_dict.FindDouble(kTimestamp).value()) |
| .LocalExplode(&exploded); |
| generated_text_log.append( |
| base::StringPrintf(kLogTimestampFormat, exploded.year, exploded.month, |
| exploded.day_of_month, exploded.hour, |
| exploded.minute, exploded.second)); |
| |
| const base::Value::Dict* details = event_dict.FindDict(kDetails); |
| switch (static_cast<LogEventType>(type)) { |
| case LogEventType::kTransition: { |
| const RmadState::StateCase from_state = |
| static_cast<RmadState::StateCase>( |
| details->FindInt(kFromStateId).value()); |
| const RmadState::StateCase to_state = static_cast<RmadState::StateCase>( |
| details->FindInt(kToStateId).value()); |
| generated_text_log.append(base::StringPrintf(kLogTransitionFormat, |
| GetStateName(from_state), |
| GetStateName(to_state))); |
| break; |
| } |
| case LogEventType::kError: { |
| const RmadErrorCode error_code = static_cast<RmadErrorCode>( |
| details->FindInt(kOccurredError).value()); |
| generated_text_log.append(base::StringPrintf( |
| kLogErrorFormat, |
| GetStateName(static_cast<RmadState::StateCase>(current_state_id)), |
| RmadErrorCode_Name(error_code).c_str())); |
| break; |
| } |
| case LogEventType::kData: { |
| const RmadState::StateCase current_state = |
| static_cast<RmadState::StateCase>(current_state_id); |
| generated_text_log.append(base::StringPrintf( |
| kLogDetailPrefixFormat, GetStateName(current_state))); |
| |
| switch (current_state) { |
| case RmadState::kWelcome: { |
| const std::optional<bool> is_compliant = |
| details->FindBool(kLogIsCompliant); |
| |
| // `is_compliant` only has a value from the hardware verifier |
| // event. |
| if (is_compliant.has_value()) { |
| if (is_compliant.value()) { |
| generated_text_log.append(kLogNoUnqualifiedComponentsString); |
| } else { |
| const std::string unqualified_components = |
| *details->FindString(kLogUnqualifiedComponents); |
| generated_text_log.append( |
| base::StringPrintf(kLogUnqualifiedComponentsDetectedFormat, |
| unqualified_components.c_str())); |
| } |
| } else { |
| generated_text_log.append(kLogRepairStartString); |
| } |
| break; |
| } |
| case RmadState::kComponentsRepair: { |
| const bool is_mlb_repair = |
| details->FindBool(kLogReworkSelected).value(); |
| if (is_mlb_repair) { |
| generated_text_log.append(kLogSelectComponentsReworkString); |
| } else { |
| const base::Value::List* components = |
| details->FindList(kLogReplacedComponents); |
| const std::string component_list = JoinValueList( |
| components, |
| [](const base::Value& value) { return value.GetString(); }, |
| ", "); |
| generated_text_log.append(base::StringPrintf( |
| kLogSelectComponentsFormat, component_list.c_str())); |
| } |
| break; |
| } |
| case RmadState::kDeviceDestination: { |
| generated_text_log.append(base::StringPrintf( |
| kLogChooseDeviceDestinationFormat, |
| (*details->FindString(kLogDestination)).c_str())); |
| break; |
| } |
| case RmadState::kWipeSelection: { |
| const std::string wipe_device = |
| details->FindBool(kLogWipeDevice).value() ? "wipe" : "keep"; |
| generated_text_log.append(base::StringPrintf( |
| kLogWipeSelectionFormat, wipe_device.c_str())); |
| break; |
| } |
| case RmadState::kWpDisableMethod: { |
| generated_text_log.append(base::StringPrintf( |
| kLogWpDisableFormat, |
| (*details->FindString(kLogWpDisableMethod)).c_str())); |
| break; |
| } |
| case RmadState::kWpDisableRsu: { |
| generated_text_log.append(base::StringPrintf( |
| kLogRsuChallengeFormat, |
| (*details->FindString(kLogRsuChallengeCode)).c_str())); |
| break; |
| } |
| case RmadState::kRestock: { |
| generated_text_log.append( |
| details->FindBool(kLogRestockOption).value() |
| ? kLogRestockShutdownString |
| : kLogRestockContinueString); |
| break; |
| } |
| case RmadState::kSetupCalibration: { |
| std::string instruction_string; |
| const CalibrationSetupInstruction setup_instruction = |
| static_cast<CalibrationSetupInstruction>( |
| details->FindInt(kLogCalibrationSetupInstruction).value()); |
| switch (setup_instruction) { |
| case RMAD_CALIBRATION_INSTRUCTION_PLACE_BASE_ON_FLAT_SURFACE: { |
| instruction_string = kLogCalibrationSetupBaseString; |
| break; |
| } |
| case RMAD_CALIBRATION_INSTRUCTION_PLACE_LID_ON_FLAT_SURFACE: { |
| instruction_string = kLogCalibrationSetupLidString; |
| break; |
| } |
| default: { |
| instruction_string = kLogCalibrationSetupUnknownString; |
| break; |
| } |
| } |
| generated_text_log.append(instruction_string); |
| break; |
| } |
| case RmadState::kCheckCalibration: { |
| const base::Value::List* components = |
| details->FindList(kLogCalibrationComponents); |
| const std::string component_list = JoinValueList( |
| components, |
| [](const base::Value& value) { |
| return GetCalibrationStatusString(value.GetDict()); |
| }, |
| ", "); |
| generated_text_log.append(base::StringPrintf( |
| kLogCalibrationFormat, component_list.c_str())); |
| break; |
| } |
| case RmadState::kUpdateRoFirmware: { |
| const FirmwareUpdateStatus status = |
| static_cast<FirmwareUpdateStatus>( |
| details->FindInt(kFirmwareStatus).value()); |
| auto it = kFirmwareUpdateStatusMap.find(status); |
| CHECK(it != kFirmwareUpdateStatusMap.end()); |
| generated_text_log.append(it->second.data()); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| generated_text_log.append("\n"); |
| } |
| |
| return generated_text_log; |
| } |
| |
| } // namespace |
| |
| std::string GenerateLogsText(scoped_refptr<JsonStore> json_store) { |
| return GenerateTextLogString(json_store); |
| } |
| |
| std::string GenerateLogsJson(scoped_refptr<JsonStore> json_store) { |
| base::Value logs(base::Value::Type::DICT); |
| json_store->GetValue(kLogs, &logs); |
| if (!logs.is_dict()) { |
| return ""; |
| } |
| |
| std::string output; |
| JSONStringValueSerializer serializer(&output); |
| serializer.set_pretty_print(true); |
| serializer.Serialize(logs); |
| return output; |
| } |
| |
| bool RecordStateTransitionToLogs(scoped_refptr<JsonStore> json_store, |
| RmadState::StateCase from_state, |
| RmadState::StateCase to_state) { |
| base::Value::Dict details; |
| details.Set(kFromStateId, static_cast<int>(from_state)); |
| details.Set(kToStateId, static_cast<int>(to_state)); |
| |
| return AddEventToJson(json_store, from_state, LogEventType::kTransition, |
| std::move(details)); |
| } |
| |
| bool RecordOccurredErrorToLogs(scoped_refptr<JsonStore> json_store, |
| RmadState::StateCase current_state, |
| RmadErrorCode error) { |
| base::Value::Dict details; |
| details.Set(kOccurredError, static_cast<int>(error)); |
| |
| return AddEventToJson(json_store, current_state, LogEventType::kError, |
| std::move(details)); |
| } |
| |
| bool RecordRepairStartToLogs(scoped_refptr<JsonStore> json_store) { |
| // Check to make sure the repair start was not already recorded. |
| base::Value logs(base::Value::Type::DICT); |
| json_store->GetValue(kLogs, &logs); |
| const base::Value::List* events = logs.GetDict().FindList(kEvents); |
| if (events) { |
| for (const base::Value& value : *events) { |
| const base::Value::Dict& event = value.GetDict(); |
| if (event.FindInt(kType) == static_cast<int>(LogEventType::kData) && |
| event.FindInt(kStateId) == static_cast<int>(RmadState::kWelcome)) { |
| return false; |
| } |
| } |
| } |
| |
| base::Value::Dict details; |
| return AddEventToJson(json_store, RmadState::kWelcome, LogEventType::kData, |
| std::move(details)); |
| } |
| |
| bool RecordUnqualifiedComponentsToLogs( |
| scoped_refptr<JsonStore> json_store, |
| bool is_compliant, |
| const std::string& unqualified_components) { |
| base::Value::Dict details; |
| details.Set(kLogIsCompliant, is_compliant); |
| details.Set(kLogUnqualifiedComponents, unqualified_components); |
| |
| return AddEventToJson(json_store, RmadState::kWelcome, LogEventType::kData, |
| std::move(details)); |
| } |
| |
| bool RecordSelectedComponentsToLogs( |
| scoped_refptr<JsonStore> json_store, |
| const std::vector<std::string>& replaced_components, |
| bool is_mlb_repair) { |
| base::Value::Dict details; |
| details.Set(kLogReplacedComponents, ConvertToValue(replaced_components)); |
| details.Set(kLogReworkSelected, is_mlb_repair); |
| |
| return AddEventToJson(json_store, RmadState::kComponentsRepair, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| bool RecordDeviceDestinationToLogs(scoped_refptr<JsonStore> json_store, |
| const std::string& device_destination) { |
| base::Value::Dict details; |
| details.Set(kLogDestination, device_destination); |
| |
| return AddEventToJson(json_store, RmadState::kDeviceDestination, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| bool RecordWipeDeviceToLogs(scoped_refptr<JsonStore> json_store, |
| bool wipe_device) { |
| base::Value::Dict details; |
| details.Set(kLogWipeDevice, wipe_device); |
| |
| return AddEventToJson(json_store, RmadState::kWipeSelection, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| bool RecordWpDisableMethodToLogs(scoped_refptr<JsonStore> json_store, |
| const std::string& wp_disable_method) { |
| base::Value::Dict details; |
| details.Set(kLogWpDisableMethod, wp_disable_method); |
| |
| return AddEventToJson(json_store, RmadState::kWpDisableMethod, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| bool RecordRsuChallengeCodeToLogs(scoped_refptr<JsonStore> json_store, |
| const std::string& challenge_code, |
| const std::string& hwid) { |
| base::Value::Dict details; |
| details.Set(kLogRsuChallengeCode, challenge_code); |
| details.Set(kLogRsuHwid, hwid); |
| |
| return AddEventToJson(json_store, RmadState::kWpDisableRsu, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| bool RecordRestockOptionToLogs(scoped_refptr<JsonStore> json_store, |
| bool restock) { |
| base::Value::Dict details; |
| details.Set(kLogRestockOption, restock); |
| |
| return AddEventToJson(json_store, RmadState::kRestock, LogEventType::kData, |
| std::move(details)); |
| } |
| |
| bool RecordCalibrationSetupInstructionToLogs( |
| scoped_refptr<JsonStore> json_store, |
| CalibrationSetupInstruction instruction) { |
| base::Value::Dict details; |
| details.Set(kLogCalibrationSetupInstruction, static_cast<int>(instruction)); |
| |
| return AddEventToJson(json_store, RmadState::kSetupCalibration, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| bool RecordComponentCalibrationStatusToLogs( |
| scoped_refptr<JsonStore> json_store, |
| const std::vector<std::pair<std::string, LogCalibrationStatus>>& |
| component_statuses) { |
| base::Value::List components; |
| for (auto& component_status : component_statuses) { |
| base::Value::Dict component; |
| component.Set(kLogComponent, component_status.first); |
| component.Set(kLogCalibrationStatus, |
| static_cast<int>(component_status.second)); |
| components.Append(std::move(component)); |
| } |
| |
| base::Value::Dict details; |
| details.Set(kLogCalibrationComponents, std::move(components)); |
| |
| return AddEventToJson(json_store, RmadState::kCheckCalibration, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| bool RecordFirmwareUpdateStatusToLogs(scoped_refptr<JsonStore> json_store, |
| FirmwareUpdateStatus status) { |
| // Check to make sure the firmware complete was not already recorded. |
| if (status == FirmwareUpdateStatus::kFirmwareComplete) { |
| base::Value logs(base::Value::Type::DICT); |
| json_store->GetValue(kLogs, &logs); |
| const base::Value::List* events = logs.GetDict().FindList(kEvents); |
| if (events) { |
| for (const base::Value& value : *events) { |
| const base::Value::Dict& event = value.GetDict(); |
| if (event.FindInt(kType) == static_cast<int>(LogEventType::kData) && |
| event.FindInt(kStateId) == |
| static_cast<int>(RmadState::kUpdateRoFirmware) && |
| event.FindDict(kDetails)->FindInt(kFirmwareStatus) == |
| static_cast<int>(FirmwareUpdateStatus::kFirmwareComplete)) { |
| return false; |
| } |
| } |
| } |
| } |
| |
| base::Value::Dict details; |
| details.Set(kFirmwareStatus, static_cast<int>(status)); |
| |
| return AddEventToJson(json_store, RmadState::kUpdateRoFirmware, |
| LogEventType::kData, std::move(details)); |
| } |
| |
| } // namespace rmad |