1 #pragma once 2 3 #include "async_resp.hpp" 4 #include "http_response.hpp" 5 #include "logging.hpp" 6 #include "redfishoemrule.hpp" 7 #include "sub_request.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 { StringLiteralredfish::StringLiteral34 constexpr StringLiteral(const char (&str)[N]) 35 { 36 std::copy_n(str, N, value); 37 } 38 operator std::string_viewredfish::StringLiteral39 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> newRule(HttpVerb method)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. PerMethodredfish::OemRouter::PerMethod123 PerMethod() : rules(1) {} 124 internalAddredfish::OemRouter::PerMethod125 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 findRouteByPerMethod(std::string_view url,const PerMethod & perMethod)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 findRoute(const SubRequest & req) const174 FindRouteResponse findRoute(const SubRequest& 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 = 189 findRouteByPerMethod(req.url(), perMethods[reqMethodIndex]); 190 if (!route.fragmentRules.empty()) 191 { 192 findRoute.route = route; 193 } 194 else 195 { 196 BMCWEB_LOG_DEBUG( 197 "No fragments for url {}, method {}", req.url(), 198 httpVerbToString(static_cast<HttpVerb>(reqMethodIndex))); 199 } 200 201 return findRoute; 202 } 203 validate()204 void validate() 205 { 206 for (PerMethod& perMethod : perMethods) 207 { 208 perMethod.trie.validate(); 209 } 210 } 211 debugPrint()212 void debugPrint() 213 { 214 for (size_t i = 0; i < perMethods.size(); i++) 215 { 216 BMCWEB_LOG_CRITICAL("{}", 217 httpVerbToString(static_cast<HttpVerb>(i))); 218 perMethods[i].trie.debugPrint(); 219 } 220 } 221 handle(const std::shared_ptr<SubRequest> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp) const222 void handle(const std::shared_ptr<SubRequest>& req, 223 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const 224 { 225 BMCWEB_LOG_DEBUG("Checking OEM routes"); 226 FindRouteResponse foundRoute = findRoute(*req); 227 std::vector<OemBaseRule*> fragments = 228 std::move(foundRoute.route.fragmentRules); 229 std::vector<std::string> params = std::move(foundRoute.route.params); 230 if (!fragments.empty()) 231 { 232 std::function<void(crow::Response&)> handler = 233 asyncResp->res.releaseCompleteRequestHandler(); 234 auto multiResp = std::make_shared<bmcweb::AsyncResp>(); 235 multiResp->res.setCompleteRequestHandler(std::move(handler)); 236 237 // Copy so that they exists when completion handler is called. 238 auto uriFragments = 239 std::make_shared<std::vector<OemBaseRule*>>(fragments); 240 auto uriParams = std::make_shared<std::vector<std::string>>(params); 241 242 asyncResp->res.setCompleteRequestHandler(std::bind_front( 243 query_param::MultiAsyncResp::startMultiFragmentHandle, req, 244 multiResp, uriFragments, uriParams)); 245 } 246 else 247 { 248 BMCWEB_LOG_DEBUG("No OEM routes found"); 249 } 250 } 251 252 private: 253 std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods; 254 }; 255 } // namespace redfish 256