Aggregation: Detect and fix all URI properties

There are a number of properties of Type "string (uri)" for which we
do not currently support adding prefixes.  This patch adds support
for all existing URI properties which are missed by the existing
implementation.

This change will be needed by future patches which will expand
aggregation support to all top level collections defined by the
schema.  Those collections that are not currently supported include
properties whose URIs should be fixed, but would be missed by the
existing implementation.

Tested:
New unit test passes.
URI properties are still handled correctly.

```shell
curl localhost/redfish/v1/Chassis/5B247A_<chassisID>
{
  "@odata.id": "/redfish/v1/Chassis/5B247A_<chassisID>",
  "@odata.type": "#Chassis.v1_16_0.Chassis",
  "Actions": {
    "#Chassis.Reset": {
      "@Redfish.ActionInfo": "/redfish/v1/Chassis/5B247A_<chassisID>/ResetActionInfo",
      "target": "/redfish/v1/Chassis/5B247A_<chassisID>/Actions/Chassis.Reset"
    }
  },
  ...
}
```

Signed-off-by: Carson Labrado <clabrado@google.com>
Change-Id: I3b3e06ee3191564d266598f7bc9f1641e6fcb333
diff --git a/include/async_resolve.hpp b/include/async_resolve.hpp
index 8a5cc4e..578d9a9 100644
--- a/include/async_resolve.hpp
+++ b/include/async_resolve.hpp
@@ -1,6 +1,7 @@
 #pragma once
 #include <boost/asio/ip/address.hpp>
 #include <boost/asio/ip/basic_endpoint.hpp>
+#include <boost/asio/ip/tcp.hpp>
 #include <sdbusplus/message.hpp>
 
 #include <charconv>
diff --git a/meson.build b/meson.build
index f62da1e..4c03ef9 100644
--- a/meson.build
+++ b/meson.build
@@ -367,6 +367,7 @@
   'test/include/multipart_test.cpp',
   'test/include/openbmc_dbus_rest_test.cpp',
   'test/redfish-core/include/privileges_test.cpp',
+  'test/redfish-core/include/redfish_aggregator_test.cpp',
   'test/redfish-core/include/registries_test.cpp',
   'test/redfish-core/include/utils/hex_utils_test.cpp',
   'test/redfish-core/include/utils/ip_utils_test.cpp',
diff --git a/redfish-core/include/redfish_aggregator.hpp b/redfish-core/include/redfish_aggregator.hpp
index b91498e..99da8ee 100644
--- a/redfish-core/include/redfish_aggregator.hpp
+++ b/redfish-core/include/redfish_aggregator.hpp
@@ -6,6 +6,8 @@
 #include <http_client.hpp>
 #include <http_connection.hpp>
 
+#include <array>
+
 namespace redfish
 {
 
@@ -15,6 +17,38 @@
     NoLocalHandle
 };
 
+// clang-format off
+// These are all of the properties as of version 2022.2 of the Redfish Resource
+// and Schema Guide whose Type is "string (URI)" and the name does not end in a
+// case-insensitive form of "uri".  That version of the schema is associated
+// with version 1.16.0 of the Redfish Specification.  Going forward, new URI
+// properties should end in URI so this list should not need to be maintained as
+// the spec is updated.  NOTE: These have been pre-sorted in order to be
+// compatible with binary search
+constexpr std::array nonUriProperties{
+    "@Redfish.ActionInfo",
+    // "@odata.context", // We can't fix /redfish/v1/$metadata URIs
+    "@odata.id",
+    // "Destination", // Only used by EventService and won't be a Redfish URI
+    // "HostName", // Isn't actually a Redfish URI
+    "Image",
+    "MetricProperty",
+    "OriginOfCondition",
+    "TaskMonitor",
+    "target", // normal string, but target URI for POST to invoke an action
+};
+// clang-format on
+
+// Determines if the passed property contains a URI.  Those property names
+// either end with a case-insensitive version of "uri" or are specifically
+// defined in the above array.
+inline bool isPropertyUri(const std::string_view propertyName)
+{
+    return boost::iends_with(propertyName, "uri") ||
+           std::binary_search(nonUriProperties.begin(), nonUriProperties.end(),
+                              propertyName);
+}
+
 static void addPrefixToItem(nlohmann::json& item, std::string_view prefix)
 {
     std::string* strValue = item.get_ptr<std::string*>();
@@ -87,30 +121,8 @@
     }
 }
 
-// We need to attempt to update all URIs under Actions
-static void addPrefixesToActions(nlohmann::json& json, std::string_view prefix)
-{
-    nlohmann::json::object_t* object =
-        json.get_ptr<nlohmann::json::object_t*>();
-    if (object != nullptr)
-    {
-        for (std::pair<const std::string, nlohmann::json>& item : *object)
-        {
-            std::string* strValue = item.second.get_ptr<std::string*>();
-            if (strValue != nullptr)
-            {
-                addPrefixToItem(item.second, prefix);
-            }
-            else
-            {
-                addPrefixesToActions(item.second, prefix);
-            }
-        }
-    }
-}
-
 // Search the json for all URIs and add the supplied prefix if the URI is for
-// and aggregated resource.
+// an aggregated resource.
 static void addPrefixes(nlohmann::json& json, std::string_view prefix)
 {
     nlohmann::json::object_t* object =
@@ -119,19 +131,12 @@
     {
         for (std::pair<const std::string, nlohmann::json>& item : *object)
         {
-            if (item.first == "Actions")
+            if (isPropertyUri(item.first))
             {
-                addPrefixesToActions(item.second, prefix);
+                addPrefixToItem(item.second, prefix);
                 continue;
             }
 
-            // TODO: Update with the uri naming language from the specification
-            // when it gets released in 1.17
-            if ((item.first == "@odata.id") ||
-                boost::algorithm::iends_with(item.first, "uri"))
-            {
-                addPrefixToItem(item.second, prefix);
-            }
             // Recusively parse the rest of the json
             addPrefixes(item.second, prefix);
         }
diff --git a/test/redfish-core/include/redfish_aggregator_test.cpp b/test/redfish-core/include/redfish_aggregator_test.cpp
new file mode 100644
index 0000000..e5c7181
--- /dev/null
+++ b/test/redfish-core/include/redfish_aggregator_test.cpp
@@ -0,0 +1,41 @@
+#include "redfish_aggregator.hpp"
+
+#include <gtest/gtest.h> // IWYU pragma: keep
+
+namespace redfish
+{
+
+TEST(IsPropertyUri, SupportedPropertyReturnsTrue)
+{
+    EXPECT_TRUE(isPropertyUri("@Redfish.ActionInfo"));
+    EXPECT_TRUE(isPropertyUri("@odata.id"));
+    EXPECT_TRUE(isPropertyUri("Image"));
+    EXPECT_TRUE(isPropertyUri("MetricProperty"));
+    EXPECT_TRUE(isPropertyUri("OriginOfCondition"));
+    EXPECT_TRUE(isPropertyUri("TaskMonitor"));
+    EXPECT_TRUE(isPropertyUri("target"));
+}
+
+TEST(IsPropertyUri, CaseInsensitiveURIReturnsTrue)
+{
+    EXPECT_TRUE(isPropertyUri("AdditionalDataURI"));
+    EXPECT_TRUE(isPropertyUri("DataSourceUri"));
+    EXPECT_TRUE(isPropertyUri("uri"));
+    EXPECT_TRUE(isPropertyUri("URI"));
+}
+
+TEST(IsPropertyUri, SpeificallyIgnoredPropertyReturnsFalse)
+{
+    EXPECT_FALSE(isPropertyUri("@odata.context"));
+    EXPECT_FALSE(isPropertyUri("Destination"));
+    EXPECT_FALSE(isPropertyUri("HostName"));
+}
+
+TEST(IsPropertyUri, UnsupportedPropertyReturnsFalse)
+{
+    EXPECT_FALSE(isPropertyUri("Name"));
+    EXPECT_FALSE(isPropertyUri("Health"));
+    EXPECT_FALSE(isPropertyUri("Id"));
+}
+
+} // namespace redfish