1 #pragma once 2 3 #include "async_resp.hpp" 4 #include "http_request.hpp" 5 #include "http_response.hpp" 6 #include "logging.hpp" 7 #include "redfishoemrule.hpp" 8 #include "sub_route_trie.hpp" 9 #include "utility.hpp" 10 #include "utils/query_param.hpp" 11 #include "verb.hpp" 12 13 #include <array> 14 #include <cstddef> 15 #include <cstdint> 16 #include <functional> 17 #include <memory> 18 #include <optional> 19 #include <stdexcept> 20 #include <string> 21 #include <string_view> 22 #include <utility> 23 #include <vector> 24 25 namespace redfish 26 { 27 28 // Helper struct to allow parsing string literals at compile time until 29 // std::string is supported in constexpr context. 30 // NOLINTBEGIN 31 template <size_t N> 32 struct StringLiteral 33 { 34 constexpr StringLiteral(const char (&str)[N]) 35 { 36 std::copy_n(str, N, value); 37 } 38 39 constexpr operator std::string_view() const 40 { 41 return std::string_view(std::data(value), N - 1); 42 } 43 44 char value[N]; 45 }; 46 // Explicit deduction guide to prevent Clang warnings 47 template <size_t N> 48 StringLiteral(const char (&)[N]) -> StringLiteral<N>; 49 // NOLINTEND 50 51 class OemRouter 52 { 53 using SubRouteTrie = crow::SubRouteTrie<crow::SubRouteNode>; 54 55 public: 56 OemRouter() = default; 57 58 template <StringLiteral URI> 59 constexpr auto& newRule(HttpVerb method) 60 { 61 auto& perMethod = perMethods[static_cast<size_t>(method)]; 62 constexpr std::string_view rule = URI; 63 constexpr uint64_t numArgs = crow::utility::getParameterTag(rule); 64 65 if constexpr (numArgs == 0) 66 { 67 using RuleT = OemRule<>; 68 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 69 RuleT* ptr = ruleObject.get(); 70 perMethod.internalAdd(rule, std::move(ruleObject)); 71 return *ptr; 72 } 73 else if constexpr (numArgs == 1) 74 { 75 using RuleT = OemRule<std::string>; 76 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 77 RuleT* ptr = ruleObject.get(); 78 perMethod.internalAdd(rule, std::move(ruleObject)); 79 return *ptr; 80 } 81 else if constexpr (numArgs == 2) 82 { 83 using RuleT = OemRule<std::string, std::string>; 84 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 85 RuleT* ptr = ruleObject.get(); 86 perMethod.internalAdd(rule, std::move(ruleObject)); 87 return *ptr; 88 } 89 else if constexpr (numArgs == 3) 90 { 91 using RuleT = OemRule<std::string, std::string, std::string>; 92 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 93 RuleT* ptr = ruleObject.get(); 94 perMethod.internalAdd(rule, std::move(ruleObject)); 95 return *ptr; 96 } 97 else if constexpr (numArgs == 4) 98 { 99 using RuleT = 100 OemRule<std::string, std::string, std::string, std::string>; 101 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 102 RuleT* ptr = ruleObject.get(); 103 perMethod.internalAdd(rule, std::move(ruleObject)); 104 return *ptr; 105 } 106 else 107 { 108 using RuleT = OemRule<std::string, std::string, std::string, 109 std::string, std::string>; 110 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 111 RuleT* ptr = ruleObject.get(); 112 perMethod.internalAdd(rule, std::move(ruleObject)); 113 return *ptr; 114 } 115 } 116 117 struct PerMethod 118 { 119 std::vector<std::unique_ptr<OemBaseRule>> rules; 120 SubRouteTrie trie; 121 // rule index 0 has special meaning; preallocate it to avoid 122 // duplication. 123 PerMethod() : rules(1) {} 124 125 void internalAdd(std::string_view rule, 126 std::unique_ptr<OemBaseRule>&& ruleObject) 127 { 128 rules.emplace_back(std::move(ruleObject)); 129 trie.add(rule, static_cast<unsigned>(rules.size() - 1U)); 130 // request to /resource/#/frag matches /resource#/frag 131 size_t hashPos = rule.find("/#/"); 132 if (hashPos != std::string_view::npos) 133 { 134 std::string url(rule.substr(0, hashPos)); 135 url += '#'; 136 url += rule.substr(hashPos + 2); // Skip "/#" (2 characters) 137 std::string_view fragRule = url; 138 trie.add(fragRule, static_cast<unsigned>(rules.size() - 1U)); 139 } 140 } 141 }; 142 143 struct FindRoute 144 { 145 std::vector<OemBaseRule*> fragmentRules; 146 std::vector<std::string> params; 147 }; 148 149 struct FindRouteResponse 150 { 151 FindRoute route; 152 }; 153 154 static FindRoute findRouteByPerMethod(std::string_view url, 155 const PerMethod& perMethod) 156 { 157 FindRoute route; 158 159 SubRouteTrie::FindResult found = perMethod.trie.find(url); 160 route.params = std::move(found.params); 161 for (auto fragmentRuleIndex : found.fragmentRuleIndexes) 162 { 163 if (fragmentRuleIndex >= perMethod.rules.size()) 164 { 165 throw std::runtime_error("Trie internal structure corrupted!"); 166 } 167 route.fragmentRules.emplace_back( 168 (perMethod.rules[fragmentRuleIndex]).get()); 169 } 170 171 return route; 172 } 173 174 FindRouteResponse findRoute(const crow::Request& req) const 175 { 176 FindRouteResponse findRoute; 177 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 178 if (!verb) 179 { 180 return findRoute; 181 } 182 size_t reqMethodIndex = static_cast<size_t>(*verb); 183 if (reqMethodIndex >= perMethods.size()) 184 { 185 return findRoute; 186 } 187 188 FindRoute route = findRouteByPerMethod(req.url().encoded_path(), 189 perMethods[reqMethodIndex]); 190 if (!route.fragmentRules.empty()) 191 { 192 findRoute.route = route; 193 } 194 else 195 { 196 BMCWEB_LOG_DEBUG( 197 "No fragments for for url {}, method {}", 198 req.url().encoded_path(), 199 httpVerbToString(static_cast<HttpVerb>(reqMethodIndex))); 200 } 201 202 return findRoute; 203 } 204 205 void validate() 206 { 207 for (PerMethod& perMethod : perMethods) 208 { 209 perMethod.trie.validate(); 210 } 211 } 212 213 void debugPrint() 214 { 215 for (size_t i = 0; i < perMethods.size(); i++) 216 { 217 BMCWEB_LOG_CRITICAL("{}", 218 httpVerbToString(static_cast<HttpVerb>(i))); 219 perMethods[i].trie.debugPrint(); 220 } 221 } 222 223 void handle(const crow::Request& req, 224 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const 225 { 226 BMCWEB_LOG_DEBUG("Checking OEM routes"); 227 FindRouteResponse foundRoute = findRoute(req); 228 std::vector<OemBaseRule*> fragments = 229 std::move(foundRoute.route.fragmentRules); 230 std::vector<std::string> params = std::move(foundRoute.route.params); 231 if (!fragments.empty()) 232 { 233 std::function<void(crow::Response&)> handler = 234 asyncResp->res.releaseCompleteRequestHandler(); 235 auto multiResp = std::make_shared<bmcweb::AsyncResp>(); 236 multiResp->res.setCompleteRequestHandler(std::move(handler)); 237 238 // Copy so that they exists when completion handler is called. 239 auto uriFragments = 240 std::make_shared<std::vector<OemBaseRule*>>(fragments); 241 auto uriParams = std::make_shared<std::vector<std::string>>(params); 242 243 asyncResp->res.setCompleteRequestHandler(std::bind_front( 244 query_param::MultiAsyncResp::startMultiFragmentHandle, 245 std::make_shared<crow::Request>(req), multiResp, uriFragments, 246 uriParams)); 247 } 248 else 249 { 250 BMCWEB_LOG_DEBUG("No OEM routes found"); 251 } 252 } 253 254 private: 255 std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods; 256 }; 257 } // namespace redfish 258