Restrict Redfish expand query features when RDE devices are present

Google-Bug-Id:553082322
PiperOrigin-RevId: 971521741
Change-Id: I29b1e0862015974c705223035a585e1d4bf54a6c
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index 25dcef9..412334c 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -25,10 +25,13 @@
 #include "app.hpp"
 #include "http_request.hpp"
 #include "async_resp.hpp"
+#include "dbus_utility.hpp"
 #include "persistent_data.hpp"
 #include "query.hpp"
 #include "registries/privilege_registry.hpp"
 #include <nlohmann/json.hpp>
+#include "managed_store.hpp"
+#include "managed_store_types.hpp"
 
 namespace redfish {
 
@@ -84,22 +87,43 @@
   nlohmann::json& protocolFeatures =
       asyncResp->res.jsonValue["ProtocolFeaturesSupported"];
   protocolFeatures["ExcerptQuery"] = false;
-
-  protocolFeatures["ExpandQuery"]["ExpandAll"] =
-      bmcwebInsecureEnableQueryParams;
-  // This is the maximum level defined in ServiceRoot.v1_13_0.json
-  if (bmcwebInsecureEnableQueryParams) {
-    protocolFeatures["ExpandQuery"]["MaxLevels"] = 6;
-  }
-  protocolFeatures["ExpandQuery"]["Levels"] = bmcwebInsecureEnableQueryParams;
-  protocolFeatures["ExpandQuery"]["Links"] = bmcwebInsecureEnableQueryParams;
-  protocolFeatures["ExpandQuery"]["NoLinks"] = bmcwebInsecureEnableQueryParams;
   protocolFeatures["FilterQuery"] = true;
   protocolFeatures["TopSkipQuery"] = true;
   protocolFeatures["OnlyMemberQuery"] = true;
   protocolFeatures["SelectQuery"] = true;
   protocolFeatures["DeepOperations"]["DeepPOST"] = false;
   protocolFeatures["DeepOperations"]["DeepPATCH"] = false;
+
+  managedStore::ManagedObjectStoreContext requestContext(asyncResp);
+  managedStore::GetManagedObjectStore()->getSubTreePaths(
+      "/xyz/openbmc_project/rde_devices", 0, {}, requestContext,
+      [asyncResp](const boost::system::error_code& ec,
+                  const dbus::utility::MapperGetSubTreePathsResponse& objects) {
+        nlohmann::json& protocolFeatures =
+            asyncResp->res.jsonValue["ProtocolFeaturesSupported"];
+        // If we detect rde devices, then we cannot support expand queries.
+        if (!ec && !objects.empty()) {
+          protocolFeatures["ExpandQuery"]["ExpandAll"] = false;
+          protocolFeatures["ExpandQuery"]["Levels"] = false;
+          protocolFeatures["ExpandQuery"]["Links"] = false;
+          protocolFeatures["ExpandQuery"]["MaxLevels"] = 1;
+          protocolFeatures["ExpandQuery"]["NoLinks"] = false;
+          return;
+        }
+
+        protocolFeatures["ExpandQuery"]["ExpandAll"] =
+            bmcwebInsecureEnableQueryParams;
+        // This is the maximum level defined in ServiceRoot.v1_13_0.json
+        if (bmcwebInsecureEnableQueryParams) {
+          protocolFeatures["ExpandQuery"]["MaxLevels"] = 6;
+        }
+        protocolFeatures["ExpandQuery"]["Levels"] =
+            bmcwebInsecureEnableQueryParams;
+        protocolFeatures["ExpandQuery"]["Links"] =
+            bmcwebInsecureEnableQueryParams;
+        protocolFeatures["ExpandQuery"]["NoLinks"] =
+            bmcwebInsecureEnableQueryParams;
+      });
 }
 inline void handleServiceRootGet(
     App& app, const crow::Request& req,
diff --git a/test/redfish-core/lib/service_root_test.cpp b/test/redfish-core/lib/service_root_test.cpp
index 6d27991..4485371 100644
--- a/test/redfish-core/lib/service_root_test.cpp
+++ b/test/redfish-core/lib/service_root_test.cpp
@@ -1,75 +1,198 @@
 #include "service_root.hpp"
 
+#include <filesystem>
 #include <memory>
+#include <string>
+#include <system_error>
+#include <vector>
 
 #include <gtest/gtest.h>
-#include "bmcweb_config.h"
 #include "http_response.hpp"
-#include "async_resp.hpp"
+#include "test/redfish-core/lib/snapshot_fixture.hpp"
 #include <nlohmann/json.hpp>
+#include "managed_store.hpp"
+#include "managed_store_types.hpp"
+#include "test/g3/mock_managed_store.hpp"
+#include "test/g3/mock_managed_store_test.hpp"
 
 namespace redfish {
 namespace {
 
+using ::managedStore::KeyType;
+using ::managedStore::ManagedType;
+
 void assertServiceRootGet(crow::Response& res) {
-  nlohmann::json& json = res.jsonValue;
-  EXPECT_EQ(json["@odata.id"], "/redfish/v1");
-  EXPECT_EQ(json["@odata.type"], "#ServiceRoot.v1_15_0.ServiceRoot");
-  EXPECT_EQ(json["CertificateService"]["@odata.id"],
-            "/redfish/v1/CertificateService");
-  EXPECT_EQ(json["CertificateService"].size(), 1);
-  EXPECT_EQ(json["Chassis"]["@odata.id"], "/redfish/v1/Chassis");
-  EXPECT_EQ(json["Chassis"].size(), 1);
-  EXPECT_EQ(json["Id"], "RootService");
-  EXPECT_EQ(json["Links"].size(), 1);
-  EXPECT_EQ(json["Links"]["ManagerProvidingService"].size(), 1);
-  EXPECT_EQ(json["Links"]["ManagerProvidingService"]["@odata.id"],
-            "/redfish/v1/Managers/bmc");
-  EXPECT_EQ(json["Managers"]["@odata.id"], "/redfish/v1/Managers");
-  EXPECT_EQ(json["Managers"].size(), 1);
-  EXPECT_EQ(json["Name"], "Root Service");
-  EXPECT_EQ(json["RedfishVersion"], "1.9.0");
-  EXPECT_EQ(json["Storage"]["@odata.id"], "/redfish/v1/Storage");
-  EXPECT_EQ(json["Storage"].size(), 1);
-  EXPECT_EQ(json["Systems"]["@odata.id"], "/redfish/v1/Systems");
-  EXPECT_EQ(json["Systems"].size(), 1);
-  EXPECT_EQ(json["Tasks"]["@odata.id"], "/redfish/v1/TaskService");
-  EXPECT_EQ(json["Tasks"].size(), 1);
-  EXPECT_EQ(json["TelemetryService"]["@odata.id"],
-            "/redfish/v1/TelemetryService");
-  EXPECT_EQ(json["TelemetryService"].size(), 1);
-  EXPECT_EQ(json["UpdateService"]["@odata.id"], "/redfish/v1/UpdateService");
-  EXPECT_EQ(json["ProtocolFeaturesSupported"].size(), 7);
-  EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExcerptQuery"]);
-  EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["ExpandAll"],
-            bmcwebInsecureEnableQueryParams);
-  EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Levels"],
-            bmcwebInsecureEnableQueryParams);
-  EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["Links"],
-            bmcwebInsecureEnableQueryParams);
-  EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["NoLinks"],
-            bmcwebInsecureEnableQueryParams);
-  if (bmcwebInsecureEnableQueryParams) {
-    EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 5);
-    EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"]["MaxLevels"], 6);
-  } else {
-    EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 4);
-  }
-  EXPECT_TRUE(json["ProtocolFeaturesSupported"]["FilterQuery"]);
-  EXPECT_TRUE(json["ProtocolFeaturesSupported"]["OnlyMemberQuery"]);
-  EXPECT_TRUE(json["ProtocolFeaturesSupported"]["SelectQuery"]);
-  EXPECT_TRUE(json["ProtocolFeaturesSupported"]["TopSkipQuery"]);
-  EXPECT_FALSE(json["ProtocolFeaturesSupported"]["DeepOperations"]["DeepPOST"]);
-  EXPECT_FALSE(
-      json["ProtocolFeaturesSupported"]["DeepOperations"]["DeepPATCH"]);
-  EXPECT_EQ(json["ProtocolFeaturesSupported"]["DeepOperations"].size(), 2);
-  EXPECT_EQ(json.size(), 18);
+  nlohmann::json json = res.jsonValue;
+  EXPECT_FALSE(json["UUID"].empty());
+  json.erase("UUID");
+
+  EXPECT_EQ(json, nlohmann::json::parse(R"({
+    "@odata.id": "/redfish/v1",
+    "@odata.type": "#ServiceRoot.v1_15_0.ServiceRoot",
+    "Cables": {
+      "@odata.id": "/redfish/v1/Cables"
+    },
+    "CertificateService": {
+      "@odata.id": "/redfish/v1/CertificateService"
+    },
+    "Chassis": {
+      "@odata.id": "/redfish/v1/Chassis"
+    },
+    "ComponentIntegrity": {
+      "@odata.id": "/redfish/v1/ComponentIntegrity"
+    },
+    "Id": "RootService",
+    "JsonSchemas": {
+      "@odata.id": "/redfish/v1/JsonSchemas"
+    },
+    "Links": {
+      "ManagerProvidingService": {
+        "@odata.id": "/redfish/v1/Managers/bmc"
+      }
+    },
+    "Managers": {
+      "@odata.id": "/redfish/v1/Managers"
+    },
+    "Name": "Root Service",
+    "ProtocolFeaturesSupported": {
+      "DeepOperations": {
+        "DeepPATCH": false,
+        "DeepPOST": false
+      },
+      "ExcerptQuery": false,
+      "ExpandQuery": {
+        "ExpandAll": true,
+        "Levels": true,
+        "Links": true,
+        "MaxLevels": 6,
+        "NoLinks": true
+      },
+      "FilterQuery": true,
+      "OnlyMemberQuery": true,
+      "SelectQuery": true,
+      "TopSkipQuery": true
+    },
+    "RedfishVersion": "1.9.0",
+    "Storage": {
+      "@odata.id": "/redfish/v1/Storage"
+    },
+    "Systems": {
+      "@odata.id": "/redfish/v1/Systems"
+    },
+    "Tasks": {
+      "@odata.id": "/redfish/v1/TaskService"
+    },
+    "TelemetryService": {
+      "@odata.id": "/redfish/v1/TelemetryService"
+    },
+    "UpdateService": {
+      "@odata.id": "/redfish/v1/UpdateService"
+    }
+  })"));
 }
 
-TEST(HandleServiceRootGet, ServiceRootStaticAttributesAreExpected) {
-  auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
-  shareAsyncResp->res.setCompleteRequestHandler(assertServiceRootGet);
-  redfish::handleServiceRootGetImpl(shareAsyncResp);
+class ServiceRootTest : public SnapshotFixture {
+ protected:
+  void SetUp() override {
+    SnapshotFixture::SetUp();
+    std::error_code ec;
+    orig_path_ = std::filesystem::current_path(ec);
+    std::filesystem::current_path(std::filesystem::temp_directory_path(ec), ec);
+  }
+
+  void TearDown() override {
+    std::error_code ec;
+    std::filesystem::current_path(orig_path_, ec);
+    SnapshotFixture::TearDown();
+  }
+
+  std::filesystem::path orig_path_;
+};
+
+TEST_F(ServiceRootTest, HandleServiceRootHeadSetsLinkHeader) {
+  handleServiceRootHead(app_, CreateRequest(), share_async_resp_);
+  EXPECT_FALSE(
+      share_async_resp_->res.getHeaderValue("Link").empty());
+}
+
+TEST_F(ServiceRootTest, HandleServiceRootUuidPopulatesUuid) {
+  handleServiceRootUuid(share_async_resp_);
+  EXPECT_FALSE(share_async_resp_->res.jsonValue["UUID"].empty());
+}
+
+TEST_F(ServiceRootTest, HandleServiceRootGetImplMiniBmcDetected) {
+  KeyType key(ManagedType::kManagedSubtreePaths,
+              "/xyz/openbmc_project/rde_devices", 0, {});
+  std::vector<std::string> paths = {
+      "/xyz/openbmc_project/rde_devices/1_1_5_3_1",
+  };
+  ASSERT_TRUE(
+      dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+          managedStore::GetManagedObjectStore())
+          ->upsertMockObjectIntoManagedStore(
+              key, managedStore::MockManagedStoreTest::CreateValueType(paths))
+          .ok());
+
+  handleServiceRootGetImpl(share_async_resp_);
+  RunIoUntilDone();
+
+  nlohmann::json& json = share_async_resp_->res.jsonValue;
+  EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"],
+            nlohmann::json::parse(R"({
+              "ExpandAll": false,
+              "Levels": false,
+              "Links": false,
+              "MaxLevels": 1,
+              "NoLinks": false
+            })"));
+}
+
+TEST_F(ServiceRootTest, HandleServiceRootGetImplEmptyRdeDevices) {
+  KeyType key(ManagedType::kManagedSubtreePaths,
+              "/xyz/openbmc_project/rde_devices", 0, {});
+  std::vector<std::string> emptyPaths;
+  ASSERT_TRUE(
+      dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+          managedStore::GetManagedObjectStore())
+          ->upsertMockObjectIntoManagedStore(
+              key,
+              managedStore::MockManagedStoreTest::CreateValueType(emptyPaths))
+          .ok());
+
+  handleServiceRootGetImpl(share_async_resp_);
+  RunIoUntilDone();
+
+  nlohmann::json& json = share_async_resp_->res.jsonValue;
+  EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"],
+            nlohmann::json::parse(R"({
+              "ExpandAll": true,
+              "Levels": true,
+              "Links": true,
+              "MaxLevels": 6,
+              "NoLinks": true
+            })"));
+}
+
+TEST_F(ServiceRootTest, HandleServiceRootGetImplDbusErrorFallsBackToDefault) {
+  // Key not in cache simulates D-Bus / mapper error in snapshot mode
+  handleServiceRootGetImpl(share_async_resp_);
+  RunIoUntilDone();
+
+  nlohmann::json& json = share_async_resp_->res.jsonValue;
+  EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"],
+            nlohmann::json::parse(R"({
+              "ExpandAll": true,
+              "Levels": true,
+              "Links": true,
+              "MaxLevels": 6,
+              "NoLinks": true
+            })"));
+}
+
+TEST_F(ServiceRootTest, ServiceRootStaticAttributesAreExpected) {
+  handleServiceRootGet(app_, CreateRequest(), share_async_resp_);
+  RunIoUntilDone();
+  assertServiceRootGet(share_async_resp_->res);
 }
 
 }  // namespace