#vbmc - Parse memory errors from CPER

PiperOrigin-RevId: 736238411
Change-Id: Id4bd40b41dce9132e50e8bbc0a076885eef305ca
diff --git a/cper/cper_event.cc b/cper/cper_event.cc
index 9d877a9..88e71df 100644
--- a/cper/cper_event.cc
+++ b/cper/cper_event.cc
@@ -1,13 +1,16 @@
 #include "cper/cper_event.h"
 
 #include <cstdint>
+#include <optional>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "absl/log/log.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
 #include "nlohmann/json.hpp"
@@ -21,6 +24,114 @@
 constexpr int kVlogVerbosity = 1;
 }  // namespace
 
+static absl::StatusOr<std::string> GetOrigin(
+    const nlohmann::json &parsed_event) {
+  auto links_it = parsed_event.find("Links");
+  if (links_it == parsed_event.end()) {
+    return absl::UnavailableError("Links not present");
+  }
+  auto origin_it = links_it->find("OriginOfCondition");
+  if (origin_it == links_it->end()) {
+    return absl::UnavailableError("OriginOfCondition link is not present");
+  }
+  auto odata_id_it = origin_it->find("@odata.id");
+  if (odata_id_it == origin_it->end()) {
+    return absl::InvalidArgumentError(
+        "Invalid event json, OriginOfCondition does not contain @odata.id");
+  }
+  auto odata_id_ptr = odata_id_it->get_ptr<const std::string *>();
+  if (odata_id_ptr == nullptr) {
+    return absl::InvalidArgumentError(
+        "Invalid event json, @odata.id is not a string");
+  }
+  return *odata_id_ptr;
+}
+
+static absl::StatusOr<const nlohmann::json *> GetCperOem(
+    const nlohmann::json &parsed_event) {
+  auto cper_it = parsed_event.find("CPER");
+  if (cper_it == parsed_event.end()) {
+    return absl::UnavailableError("No CPER found");
+  }
+  auto cper_data_it = cper_it->find("Oem");
+  if (cper_data_it == cper_it->end()) {
+    return absl::InvalidArgumentError("CPER does not contain OEM");
+  }
+
+  if (!cper_data_it->is_object()) {
+    return absl::InvalidArgumentError("CPER OEM is not an object");
+  }
+
+  auto nvidia_it = cper_data_it->find("Nvidia");
+  if (nvidia_it == cper_data_it->end()) {
+    return absl::InvalidArgumentError("CPER OEM does not contain Nvidia");
+  }
+
+  auto odata_type_it = nvidia_it->find("@odata.type");
+  if (odata_type_it == nvidia_it->end()) {
+    LOG(WARNING) << "CPER does not contain @odata.type";
+    return &*nvidia_it;
+  }
+
+  auto odata_type_ptr = odata_type_it->get_ptr<const std::string *>();
+  if (odata_type_ptr == nullptr) {
+    return absl::InvalidArgumentError(
+        "Invalid event json, @odata.type is not a string");
+  }
+  if (!odata_type_ptr->starts_with("#NvidiaCPER.") ||
+      !odata_type_ptr->ends_with(".NvidiaCPER")) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Unexpected @odata.type ", *odata_type_ptr));
+  }
+  return &*nvidia_it;
+}
+
+static absl::StatusOr<int> GetMemoryErrors(const nlohmann::json &cper_data) {
+  auto memory_errors_it = cper_data.find("Memory");
+  if (memory_errors_it == cper_data.end()) {
+    return absl::UnavailableError("MemoryErrors is not present");
+  }
+  if (!memory_errors_it->is_object()) {
+    return absl::InvalidArgumentError("CPER/Oem/Memory is not an object");
+  }
+  return 1;
+}
+
+static absl::StatusOr<int> GetCpuErrors(const nlohmann::json &cper_data) {
+  return absl::UnavailableError(
+      "TODO:b/402483544 - CpuErrors is not yet implemented");
+}
+
+static absl::StatusOr<Event::CperEventData> ParseCperEventData(
+    const nlohmann::json &parsed_event) {
+  Event::CperEventData cper_event_data;
+
+  ASSIGN_OR_RETURN(const nlohmann::json *cper_data, GetCperOem(parsed_event));
+  absl::StatusOr<std::string> origin = GetOrigin(parsed_event);
+  if (origin.ok()) {
+    cper_event_data.origin = *std::move(origin);
+  } else {
+    LOG(WARNING) << "OriginOfCondition not found: " << origin.status();
+    if (!absl::IsUnavailable(origin.status())) {
+      return origin.status();
+    }
+  }
+
+  if (absl::StatusOr<int> memory_errors = GetMemoryErrors(*cper_data);
+      memory_errors.ok()) {
+    cper_event_data.memory_errors = *memory_errors;
+  } else if (!absl::IsUnavailable(memory_errors.status())) {
+    return memory_errors.status();
+  }
+  if (absl::StatusOr<int> cpu_errors = GetCpuErrors(*cper_data);
+      cpu_errors.ok()) {
+    cper_event_data.cpu_errors = *cpu_errors;
+  } else if (!absl::IsUnavailable(cpu_errors.status())) {
+    return cpu_errors.status();
+  }
+
+  return cper_event_data;
+}
 absl::StatusOr<std::vector<Event>> CperEvent::ParseRedfishEvent(
     absl::string_view sse_json) {
   nlohmann::json parsed = nlohmann::json::parse(sse_json, /*cb=*/nullptr,
@@ -94,7 +205,13 @@
     VLOG(kVlogVerbosity) << "EventId: " << event_id;
 
     // Check if the event contains CPER.
-    bool is_cper_event = events_it->at(i).contains("CPER");
+    absl::StatusOr<Event::CperEventData> cper_event_data =
+        ParseCperEventData(events_it->at(i));
+    if (!cper_event_data.ok() &&
+        !absl::IsUnavailable(cper_event_data.status())) {
+      return cper_event_data.status();
+    }
+
     auto message_severity_ptr =
         events_it->at(i)["MessageSeverity"].get_ptr<std::string *>();
     if (message_severity_ptr == nullptr) {
@@ -106,8 +223,12 @@
     ASSIGN_OR_RETURN(EventSeverity severity,
                      StringToEventSeverity(*message_severity_ptr));
 
+    std::optional<Event::CperEventData> cper_event_data_opt = std::nullopt;
+    if (cper_event_data.ok()) {
+      cper_event_data_opt = *std::move(cper_event_data);
+    }
     events.push_back(Event(events_it->at(i).dump(), event_id, severity,
-                           /*reason_id=*/-1, is_cper_event));
+                           /*reason_id=*/-1, std::move(cper_event_data_opt)));
   }
 
   // Validate `Id` is same as last `EventId`.
diff --git a/sse_plugin/event.h b/sse_plugin/event.h
index 923f69b..9ebe8a7 100644
--- a/sse_plugin/event.h
+++ b/sse_plugin/event.h
@@ -2,7 +2,9 @@
 #define PLATFORMS_VBMC_PLATFORM_EIGER_EVENT_H_
 
 #include <cstdint>
+#include <optional>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "absl/container/flat_hash_map.h"
@@ -49,14 +51,21 @@
 //                     Serving state
 class Event {
  public:
+  struct CperEventData {
+    std::string origin;
+    int memory_errors = 0;
+    int cpu_errors = 0;
+  };
+
   Event() = default;
   Event(absl::string_view event_json, int64_t event_id, EventSeverity severity,
-        int reason_id = kInvalidReasonId, bool is_cper_event = false)
+        int reason_id = kInvalidReasonId,
+        std::optional<CperEventData> cper_event_data = std::nullopt)
       : event_id_(event_id),
         reason_id_(reason_id),
         severity_(severity),
         event_json_(std::string(event_json)),
-        is_cper_event_(is_cper_event) {}
+        cper_event_data_(std::move(cper_event_data)) {}
 
   // Movable
   Event(Event&& other) = default;
@@ -70,7 +79,10 @@
   EventSeverity severity() const { return severity_; }
   std::string event_json() const { return event_json_; }
   int reason_id() const { return reason_id_; }
-  bool is_cper_event() const { return is_cper_event_; }
+  bool is_cper_event() const { return cper_event_data_.has_value(); }
+  const std::optional<CperEventData>& cper_event_data() const {
+    return cper_event_data_;
+  }
 
   bool IsEmpty() const { return event_id_ == 0; }
 
@@ -85,7 +97,7 @@
   int reason_id_;
   EventSeverity severity_;
   std::string event_json_;
-  bool is_cper_event_ = false;
+  std::optional<CperEventData> cper_event_data_;
 };
 
 }  // namespace milotic